` es pagar un carrying cost permanente por una comodidad de tecleo momentánea.
No es que las alternativas sean malas; es que el ahorro no compensa el coste a lo largo de los años. ERB es la opción aburrida, y aburrido —en sostenibilidad— es un cumplido.
## Mi versión
Lo de "crea más recursos en vez de acciones custom" me costó interiorizarlo, pero una vez que haces el clic, no hay vuelta atrás. Antes mis controladores se llenaban de `approve`, `publish`, `archive`, `reactivate`... y cada uno era un método especial con su propio testeo y sus propias rarezas. Ahora pienso en sustantivos: una publicación es `POST /posts/:id/publication`, un archivado es la creación de un `Archival`. Los controladores vuelven a ser aburridos y todos se parecen. Aburrido = mantenible.
Sobre ERB, suscribo cada palabra. He heredado proyectos en Slim donde, para tocar una plantilla, primero tenías que recordar la sintaxis. Multiplica eso por cada persona que pasa por el proyecto en cinco años y el ahorro de teclas sale carísimo. En mis proyectos, plantillas en el lenguaje del propio medio (HTML) y punto.
Donde soy menos purista que el libro es en lo de "una sola variable de instancia". Me parece una dirección sanísima, pero forzarla siempre puede llevar a objetos *page* artificiales. Tiendo a ello cuando la vista es compleja y dejo dos o tres ivars cuando la acción es trivial.
## Lo que viene
Ya tenemos las rutas limpias y las plantillas bien estructuradas. En el **próximo post** seguimos en la capa de presentación pero entramos en terreno polémico: **helpers, CSS y minimizar JavaScript**. Veremos dónde va de verdad la lógica de vista, por qué Copeland recela de presenters y decorators, cómo adoptar un design system y, sobre todo, su defensa de hacer el mínimo JavaScript posible apoyándose en vistas renderizadas en servidor y en Turbo.
---
# Cambio de piso: Benalmádena
URL: https://javiervalencia.net/post/cambio-de-piso-benalmadena
Benalmádena es el siguiente nombre en mi lista. Lo curioso de Benalmádena es que no es un sitio, son tres. Y la decisión real no es "mudarse a Benalmádena", es "mudarse a Benalmádena Pueblo, a Arroyo de la Miel o a Benalmádena Costa". Cada uno funciona de manera distinta, tiene precios distintos y resuelve la vida cotidiana de manera distinta. Voy a evaluarlos como si fueran tres opciones separadas.
## Las tres Benalmádenas
Benalmádena Pueblo está arriba, en la sierra, blanco, con calles empinadas y vistas al mar. Es la versión más tradicional, la que recuerda a Mijas Pueblo: turismo, terrazas, bonito de día, silencioso de noche.
Arroyo de la Miel es el corazón administrativo y de vida diaria. Tiene la estación de Cercanías, el ayuntamiento, comercio de barrio, el parque de la Paloma, mucha vida vecinal. La gente vive aquí.
Benalmádena Costa es la franja litoral. Incluye Puerto Marina, Carihuela, Torrequebrada. Es la zona más turística, con bloques altos, hoteles, vida nocturna y paseo marítimo continuo.
## Lo bueno
El tren. Igual que Fuengirola, tiene Cercanías. Eso, repito, es un argumento de peso. Arroyo de la Miel es estación cabecera de la línea, así que para ir a Málaga o al aeropuerto es trivial.
El parque de la Paloma. Es uno de los parques urbanos mejor pensados de la provincia. Animales sueltos, lago, zonas amplias, sombra. Para una familia con niños es un activo enorme. Para Penélope ya no es central, pero tener un parque así a diez minutos andando sigue siendo bonito.
La oferta familiar. Selwo Marina, Tivoli, el teleférico, las atracciones costeras. Sé que esto pesa más cuando los niños son pequeños, pero el ecosistema sigue ahí: Benalmádena ha sido históricamente un destino familiar y eso se nota en el tipo de comercio y servicios.
Carihuela. Si vas a vivir cerca de la Carihuela, tienes uno de los mejores paseos de chiringuitos clásicos de toda la costa. Y es un argumento gastronómico que hay que poner en la balanza.
La diversidad. Benalmádena tiene una mezcla muy particular de población: española, británica, escandinava, marroquí, y desde hace años una comunidad finlandesa muy presente. Para alguien que no quiere vivir en una burbuja, esto es valioso. Las hijas crecen viendo el mundo.
## Lo malo
El turismo, en Costa especialmente, es muy intenso. La franja del paseo marítimo y Puerto Marina viven en un régimen de fin de semana eterno. Si te gusta el alboroto, perfecto. Si quieres descansar, hay que escoger la zona con cuidado.
El relieve. Muchas zonas de Benalmádena están en cuesta. Hay urbanizaciones donde sin coche no vas a ningún sitio, simplemente por la pendiente. Hay edificios en los que la entrada del garaje está en una calle y la del portal en otra, dos pisos más arriba. Penélope no me lo perdonaría.
El tráfico hacia el centro. La salida de Arroyo hacia la A-7 puede ser un suplicio en hora punta. Las conexiones internas dentro del municipio, entre las tres zonas, son menos buenas de lo que parece sobre el plano.
La identidad fragmentada. Vivir en Arroyo y trabajar visualmente con Benalmádena Costa es como vivir en dos municipios distintos. La sensación de "barrio" depende mucho de en qué de los tres sitios estés. Si te equivocas en la elección de zona, no estás en Benalmádena: estás en una versión equivocada de Benalmádena para ti.
Y como en todas las opciones costeras, el alquiler está caro y el alquiler vacacional ha vaciado parte del mercado de larga estancia.
## Para una familia como la nuestra
Si lo planteo en serio, la única zona de Benalmádena que tiene sentido para nosotros es Arroyo de la Miel. Tiene tren, vida vecinal, comercio cotidiano, parque, y está suficientemente lejos del jaleo costero como para descansar. Costa la descarto: demasiado turismo, demasiado bloque alto, demasiada vida de hotel. Pueblo es bonito pero exige el coche para casi todo.
Arroyo, en cambio, podría funcionar muy bien. Penélope iría a Málaga sola en tren, las mayores también, los servicios están a mano, hay vida en la calle, y al mismo tiempo no es la presión turística de Fuengirola centro. La pega es que el mercado de alquiler en Arroyo es ajustado y los precios buenos vuelan.
## Veredicto provisional
Arroyo de la Miel se queda en la lista corta. Costa y Pueblo, fuera. Es una candidatura seria, comparable a Fuengirola, con una vida quizá un punto más vecinal y un punto menos masificada. Si encontrara un piso bueno aquí antes que en Fuengirola, no me costaría dar el paso.
---
# Cambio de piso: Fuengirola
URL: https://javiervalencia.net/post/cambio-de-piso-fuengirola
Sigo con la evaluación de sitios para mudarme. Después de Benahavís y de mirar con honestidad mi Mijas Costa actual, le toca a Fuengirola. Es probablemente el pueblo de la Costa del Sol que mejor conozco después del mío: voy al mercado, a los chiringuitos, al cine, a comer pescaíto en Los Boliches. Lo que cambia ahora es mirarlo no como visitante, sino como posible residente.
## Qué es Fuengirola
Fuengirola es la ciudad más urbana y más densa de la franja costera entre Málaga y Marbella. Tiene unos ochenta mil habitantes empadronados y muchísimos más en temporada. Es compacta, plana, fácil de andar, con un paseo marítimo de ocho kilómetros que la atraviesa de punta a punta. El centro es totalmente caminable. Tiene estación de Cercanías que conecta directamente con Málaga capital y el aeropuerto en menos de cuarenta minutos.
A diferencia de Mijas Costa, Fuengirola sí es un pueblo en el sentido clásico: con calles, con plaza, con mercado central, con gente que va andando a hacer recados. La vida no pasa por dentro de una urbanización, pasa por la calle.
## Lo bueno
El tren. Que un núcleo de la Costa del Sol tenga Cercanías y los demás no es una diferencia enorme. Significa que Penélope puede ir sola a Málaga capital cuando empiece a moverse. Significa que las mayores vienen sin pelearse con la AP-7. Significa que para ir al aeropuerto no hace falta el coche. Es de esas infraestructuras que no valoras hasta que has vivido sin ellas.
La densidad de servicios. Hospital, mercado central, centros de salud, comercio local de verdad, supermercados de varias cadenas, tiendas pequeñas, librerías, cines. Todo en metros, no en kilómetros. Para una familia con una hija adolescente en pleno desarrollo, esto cambia la vida cotidiana.
El paseo marítimo. Ocho kilómetros con tramos para todos los gustos: la zona de Los Boliches con sus chiringuitos viejos y honestos, el centro con tirones turísticos, Carvajal hacia Benalmádena más pijo. Lo recorras como lo recorras, son ocho kilómetros de ruta segura para correr, andar o pasear.
La autenticidad relativa. Pese a la cantidad de turismo, Fuengirola conserva una identidad de pueblo malagueño que otros sitios han perdido. El mercado central a media mañana sigue siendo el mercado central. El bar de la esquina sigue siendo el bar de la esquina. Hay vida vecinal real.
## Lo malo
La densidad tiene su precio. Fuengirola es ruidosa. En verano es un hervidero. Las terrazas funcionan hasta las cuatro de la madrugada. Los pisos del centro pegan con la vida nocturna por debajo. Si tu ventana da a una calle equivocada, el descanso se acaba.
El tráfico. Las entradas y salidas de la ciudad, especialmente la conexión con la A-7 y los accesos al centro, se atascan con normalidad. Aparcar en el casco es complicado todo el año, peor en verano. Si tienes coche, garaje no es opcional.
La urbanización es densa. Mucho bloque alto, mucha sombra de edificio, calles estrechas. Para alguien que viene de una urbanización con palmeras y jardines, el cambio es brusco. La sensación de "vivir en un piso" es muy distinta a la sensación de "vivir en mi urbanización".
El turismo de cercanía. Fuengirola es destino de fin de semana de toda la provincia, no solo de extranjeros. Los sábados el centro y la playa se llenan de gente que viene a pasar el día. Eso, en lo bueno, da vida; en lo malo, satura.
Y un detalle no menor: el mercado del alquiler en Fuengirola está muy tensionado, especialmente para vivienda larga estancia. Mucha oferta se ha ido al alquiler vacacional. Encontrar un piso de tres dormitorios con buenas condiciones a precio razonable no es fácil.
## Para una familia como la nuestra
Es aquí donde Fuengirola se pone interesante. Para Penélope sería un salto: amigas a las que ir andando, instituto cerca, autonomía real para moverse, oferta de actividades amplia. Para mí, que trabajo desde casa y que estoy entrando en una etapa donde valoro lo cómodo más que lo épico, tener todo a mano sin coger el coche es un argumento fuerte. Para las mayores, llegar en tren los domingos es trivial.
La pregunta es cuánto silencio estoy dispuesto a sacrificar. Llevo años en un sitio donde por la noche se oye el mar, no la calle. Pasar de eso a un piso urbano céntrico sería un cambio que el cuerpo notaría. Existe la opción intermedia: zona de Los Boliches o cerca de la estación, no en pleno casco, donde la vida es más calmada pero el centro sigue a diez minutos andando.
## Veredicto provisional
Fuengirola es la opción más sensata por logística. Tren, servicios, vida de pueblo real, todo a mano. Si encontrara un piso bueno en una zona tranquila pero con el centro a tiro de paseo, sería difícil de descartar. Sube en la lista.
---
# Rails sostenible (III): la lógica de negocio no va en los Active Records
URL: https://javiervalencia.net/post/rails-sostenible-iii-logica-de-negocio-fuera-de-active-record
*Tercera entrega de la serie **[Rails sostenible](/search?tag=rails-sostenible)**, sobre el libro de David Bryant Copeland. Tiempo de lectura estimado: 14 minutos.*
Si esta serie tuviera que reducirse a un solo post, sería este. Toda la propuesta de Copeland gira alrededor de una frase que da título a su capítulo cinco y que reaparece, ampliada, en el capítulo quince: **la lógica de negocio no va en los Active Records**. Vimos en [la primera entrega](/post/rails-sostenible-i-sostenibilidad-y-arquitectura) que Rails no le da casa a la lógica de negocio. Hoy se la construimos.
## Qué es la lógica de negocio (y por qué es peligrosa)
Copeland define la lógica de negocio como **lo que hace especial a tu aplicación**. No es el CRUD —eso lo hace cualquier framework—, sino las reglas concretas de tu dominio: cómo se calcula el precio de un pedido con descuentos y cupones, qué pasa cuando un usuario cancela una suscripción, cuándo se considera que un envío está "retrasado". Es el código que no podrías copiar de otro proyecto porque es tuyo.
Y precisamente por eso es peligroso, por tres razones que el libro desgrana:
1. **Es un imán de complejidad.** Las reglas de negocio se acumulan, se contradicen, tienen excepciones y casos límite. Es, de lejos, la parte más enrevesada del sistema.
2. **Sufre mucho *churn*.** Cambia constantemente, porque el negocio cambia constantemente. Lo que hoy es "10% de descuento a partir de 50 €" mañana es otra cosa.
3. **Un bug aquí tiene efectos amplios.** Si la lógica vive en clases muy referenciadas, un error se propaga por todo el sistema.

## El problema de meterla en los Active Records
Junta esas tres características con la realidad de un Active Record y verás el desastre. El modelo `Order` lo usa medio sistema: los controladores, las vistas, los jobs, los mailers, los tests. Es una clase **crítica y omnipresente**. Si además le metes dentro toda la lógica de cálculo de precios, validaciones contextuales y disparo de side effects, conviertes una clase ya frágil por su uso en una clase que *además* cambia cada semana.
```ruby
# El anti-patrón: el "fat model" que lo sabe todo
class Order < ApplicationRecord
belongs_to :customer
has_many :line_items
after_create :send_confirmation_email
after_create :notify_warehouse
after_update :recalculate_loyalty_points
def total
subtotal = line_items.sum(&:price)
subtotal -= discount_for(customer)
subtotal += shipping_cost
subtotal * (1 + tax_rate)
end
def discount_for(customer)
# 40 líneas de reglas de cupones, fidelidad, promociones...
end
# ...y otras 600 líneas
end
```
Esta clase mezcla **tres responsabilidades** que cambian por motivos distintos: el acceso a datos (las asociaciones), las reglas de negocio (cálculo de precios) y los efectos colaterales (emails, almacén). Cada una tiene su propio ritmo de cambio. Tenerlas pegadas significa que tocar una te obliga a entender y arriesgar las otras. Eso es lo contrario de sostenible.
## El *seam*: una costura entre Rails y tu dominio
Aquí Copeland introduce el concepto que vertebra su solución: el **seam** (costura). Un *seam* es un punto del sistema donde puedes separar dos mundos: el mundo de Rails (HTTP, base de datos, vistas) y el mundo de tu lógica de negocio. La idea es que los controladores, jobs y demás clases frontera **deleguen** la lógica de negocio a clases que no saben nada de Rails.
> Tu lógica de negocio debería poder leerse y entenderse sin necesidad de saber que existe un controlador, un request HTTP o una tabla en Postgres.
Esa frontera —el seam— es lo que te permite que cada lado evolucione a su ritmo. Los controladores cambian cuando cambia la interfaz; la lógica de negocio cambia cuando cambian las reglas; y la una no arrastra a la otra.
## Servicios stateless con nombres explícitos
¿Cómo se materializa el seam? Con una **capa de servicios**. Pero ojo, Copeland es muy específico sobre cómo deben ser estos servicios, porque "service object" en la comunidad Rails significa muchas cosas y no todas buenas:
- **Stateless:** el servicio no guarda estado entre llamadas. No tiene atributos mutables que representen "el progreso" de algo. Recibe lo que necesita, hace su trabajo, devuelve un resultado.
- **Nombres explícitos de clase:** nada de un cajón de sastre `OrderService` que acumula 30 métodos inconexos. Mejor clases con nombre propio que describan una operación del dominio.
- **Nombres explícitos de método:** el método dice qué hace en lenguaje del negocio.
Copeland propone agrupar estos servicios en una clase de dominio que actúa como punto de entrada. Por ejemplo, un objeto `Customers` con métodos como `create_customer` o `cancel_subscription`:
```ruby
# app/services/customers.rb
class Customers
def create_customer(email:, name:)
customer = Customer.create!(email: email, name: name)
Welcome.deliver_later(customer) # side effect explícito
Result.new(created: true, customer: customer)
end
end
```
Y el controlador, que vive del lado de Rails del seam, se limita a traducir el request y delegar:
```ruby
class CustomersController < ApplicationController
def create
result = Customers.new.create_customer(
email: params[:email],
name: params[:name],
)
if result.created?
redirect_to customer_path(result.customer)
else
render :new, status: :unprocessable_entity
end
end
end
```
Fíjate en lo que ha pasado: el controlador no sabe *cómo* se crea un cliente, solo que existe una operación de dominio llamada "crear cliente". El servicio no sabe que existe un controlador. La costura está limpia. Y el `Customer` (el Active Record) ha vuelto a su sitio: ser una pasarela hacia la base de datos, no el cerebro del negocio.
## Patrones que conviene evitar
El capítulo quince dedica buena parte a desaconsejar implementaciones populares de "service object" que, según Copeland, suelen empeorar las cosas:
- **El servicio con estado y un único `call`.** Ese patrón de `MyService.new(args).call` donde el objeto guarda los argumentos como atributos introduce estado mutable innecesario y oscurece qué hace cada método. Copeland prefiere métodos explícitos sobre objetos stateless.
- **Abusar de `ApplicationService` con magia compartida.** Las clases base que inyectan comportamiento mágico (`success`, `failure`, callbacks) tienen su propio carrying cost: hay que entender la base para entender cualquier servicio.
- **Confundir "un servicio por acción de controlador" con diseño.** Tener un `CreateOrderService`, `UpdateOrderService`, `DeleteOrderService` que reflejan el CRUD no aporta nada: estás renombrando el controlador. Los servicios deben modelar **operaciones del dominio**, no acciones HTTP.
La regla de fondo: el código de negocio debe **revelar comportamiento**. Al leer la clase, debes entender qué hace el negocio, no perderte en andamiaje genérico.
## Mi versión
Esta es la idea del libro que más he peleado en proyectos reales, y la que más resistencia genera, porque va contra el "Rails way" que mucha gente interioriza como dogma. El argumento que mejor me funciona no es teórico, es práctico: enseño el modelo `User` de 900 líneas y pregunto quién se atreve a tocarlo sin sudar. Nadie. Ese miedo *es* el coste de la insostenibilidad, hecho carne.
Dicho esto, también he visto el extremo opuesto: equipos que, mal interpretando el consejo, montan una capa de servicios con 200 clases `XxxService` de un solo método que son indistinguibles de funciones sueltas y que, encima, todas heredan de una base mágica imposible de seguir. Eso *también* es insostenible. La clave que Copeland repite y que yo suscribo: servicios **stateless, con nombres del dominio, sin magia**. Si tu capa de servicios no se lee como un glosario de tu negocio, algo va mal.
Mi heurística personal: el Active Record puede tener métodos de *consulta* y de *presentación de datos propios* (un `full_name`, un scope sencillo), pero en cuanto aparece un side effect, una regla con varias ramas o una operación que coordina varios objetos, eso se va al seam. El modelo guarda y lee; el servicio decide.
## Lo que viene
Hemos construido la pieza central: un seam que separa Rails de tu dominio, con servicios stateless de nombres explícitos. A partir de aquí, el libro recorre cada capa de Rails aplicando esta filosofía. En el **próximo post** empezamos por la puerta de entrada de toda petición: las **rutas y las plantillas HTML** —rutas canónicas, recursos en vez de acciones custom, HTML semántico, partials como componentes reutilizables y por qué Copeland defiende seguir usando ERB sin complejos.
---
# Cambio de piso: Mijas Costa
URL: https://javiervalencia.net/post/cambio-de-piso-mijas-costa
Cuando uno se plantea cambiar de piso, lo primero que tiene que hacer es preguntarse por qué se quiere ir. Y para responder a eso, hay que evaluar honestamente el sitio en el que vives ahora, sin dar por hecho que el problema está fuera. Yo vivo en Mijas Costa desde hace varios años. Si voy a comparar con Benahavís, Fuengirola, Estepona o cualquier otra opción, es justo que también ponga a Mijas Costa en la balanza con los mismos ojos.
## Qué es Mijas Costa
Mijas Costa no es un núcleo urbano único. Es una franja de litoral de unos doce kilómetros entre Fuengirola y Marbella, salpicada de urbanizaciones, calles residenciales y unos pocos centros con vida propia: La Cala de Mijas, Calahonda, Riviera del Sol, Calypso, El Faro. Detrás está el término municipal de Mijas, con Mijas Pueblo en la sierra, blanco y turístico, y zonas intermedias residenciales.
Es decir: vivir en "Mijas Costa" puede significar cosas muy distintas según en qué urbanización tengas el piso. La mía está a cinco minutos andando de la playa, en una zona tranquila pero cerca de comercio.
## Lo bueno
Empiezo por lo que llevo años disfrutando.
El paseo litoral. Hay que decirlo claro: el paseo de La Cala es uno de los activos más infravalorados de la zona. Cuarenta y cinco minutos de costa que puedes hacer cualquier tarde del año, sin coche, sin ruido, con el mar a la izquierda y la sierra a la derecha. Cuando lo cuento fuera nadie se lo cree.
La comunicación. Estoy a quince minutos en moto de Fuengirola y a veinte de San Pedro. La A-7 a la puerta y la AP-7 si quiero ir más rápido. El aeropuerto de Málaga a treinta minutos. Para alguien que viaja por trabajo o que coge el coche de cabecera, es un punto neurálgico.
El equilibrio entre tranquilidad y servicios. La urbanización es silenciosa, pero a cinco minutos tengo supermercados, farmacias, médicos, gasolineras, ferreterías, lo que necesite. No vivo aislado pero tampoco en medio del jaleo.
El clima dentro de la Costa del Sol es prácticamente igual en todos los pueblos costeros, así que esto no es un argumento diferenciador. Pero sí lo es la sensación de barrio dentro de la urbanización: la gente se conoce, hay vecinos de toda la vida, los niños han crecido juntos. Eso no se replica en seis meses.
## Lo malo
Voy con lo que cuesta admitir cuando llevas años en un sitio.
La temporada alta. De junio a septiembre, esto deja de ser un sitio para vivir y se convierte en un sitio que aguantas. Aparcar es una odisea, los supermercados se llenan de carros con productos congelados de turistas, las terrazas suben los precios, y la A-7 se atasca. Lo describí ya en otro post, y no he cambiado de opinión.
La dispersión. Vivir en una urbanización tiene un precio social. No hay un centro donde te encuentres a la gente. No hay calles peatonales con vida en las que pasee uno simplemente porque sí. Para tomar algo con un amigo hay que coger el coche o la moto. La vida se vuelve un poco "de chalet": vas de tu casa a un destino concreto y vuelves.
La dependencia del coche. No hay tren. El autobús pasa cuando puede. Penélope y sus amigas se mueven todo lo que pueden a pie y para el resto dependen de que alguien las lleve. Los días de lluvia se nota mucho.
Y luego está el factor "ya lo tengo todo visto". Llevas tantos años haciendo el mismo paseo que a veces lo das por hecho. La urbanización no cambia. Los vecinos van envejeciendo, los pisos se alquilan a temporada, las tiendas se renuevan poco. La rutina, que en lo bueno es estabilidad, en lo malo es inercia.
## Para una familia como la nuestra
Mijas Costa funciona muy bien para nosotros en lo práctico. Penélope tiene amigas a cinco minutos. Las mayores vienen los domingos sin atascos. Yo trabajo desde casa, tengo el paseo a la puerta y el aeropuerto cerca. La logística no falla.
Lo que no acaba de cuadrar es la sensación de que esto sigue pareciéndome un sitio de paso. Después de tantos años, la urbanización no me da raíces, me da una dirección. Y eso, mirado en frío, no es lo que querría para los próximos diez o quince años.
## Veredicto provisional
Mijas Costa es una opción funcional, conocida, sin sorpresas. Si no apareciera nada mejor, quedarme aquí no sería un mal plan: hay sitios mucho peores en los que envejecer. Pero precisamente por eso voy a seguir mirando. Si lo que encuentro fuera no es claramente mejor para nosotros, me quedo. Si lo es, no quiero que la inercia me lo impida.
---
# Cambio de piso: Benahavís
URL: https://javiervalencia.net/post/cambio-de-piso-benahavis
Llevo unos meses dándole vueltas a la idea de cambiar de piso. No salir de la Costa del Sol, pero sí buscar algo distinto a Mijas Costa, donde llevo viviendo varios años. Como ejercicio honesto conmigo mismo, voy a dedicar unos cuantos posts a evaluar las opciones que tengo en la cabeza, una por una, sin idealizarlas. Empiezo por la que sobre el papel es más bonita: Benahavís.
## Qué es Benahavís
Benahavís es un pueblo blanco encajado en la sierra, a unos quince minutos de Marbella subiendo por la garganta del Guadalmina. No tiene playa propia: el término municipal acaba antes de llegar a la costa. Lo que tiene es montaña, ríos, senderos, y un pueblo pequeño con calles estrechas, fachadas blancas y macetas con geranios. La parte alta del término, donde están La Zagaleta o El Madroñal, es zona de fincas y urbanizaciones de lujo de las que salen en revistas internacionales.
Hay una etiqueta que el propio pueblo utiliza: "el comedor de la Costa del Sol". En cinco calles cuentas treinta restaurantes, muchos de ellos buenos de verdad. La gastronomía es uno de los argumentos comerciales del municipio y se nota en la calidad de la oferta.
## Lo bueno
Lo primero que te gana de Benahavís es la sensación de respirar. Después de años de tráfico costero, ruido de obras, terrazas llenas y autovía, llegar a un sitio donde el ruido de fondo es el río y el viento entre los árboles es un cambio que se nota en el cuerpo. Para alguien introvertido, que trabaja desde casa y que no necesita que la vida pase por delante de su ventana, eso pesa.
La naturaleza está en la puerta. La garganta del Guadalmina, las charcas, los senderos hacia Istán o hacia Ronda. Salir a caminar un domingo por la mañana sin tener que coger el coche para ir a un parque natural es un lujo cotidiano. La calidad del aire es notablemente mejor que en la costa.
El pueblo es seguro, pequeño y con un tejido vecinal real. La gente se conoce. Los niños juegan en la plaza sin que nadie esté pendiente de la hora.
Y luego está la mesa. No es un detalle menor. Tener treinta restaurantes a cinco minutos andando, varios de ellos para ocasiones especiales, es algo que aquí se da por hecho y que en muchos sitios sería un lujo.
## Lo malo
Y aquí empiezan los matices.
Benahavís es caro. El pueblo en sí tiene precios contenidos comparado con las urbanizaciones, pero el alquiler de un piso decente para una familia ya supera lo que pago ahora en Mijas Costa. Si subes a las urbanizaciones residenciales, los números se van a otra liga. La idea romántica de "vivir en la sierra cerca de Marbella" tiene un peaje claro.
El segundo problema es la dependencia del coche. No hay tren, el autobús es testimonial, y todo lo que no sea pan, café y restaurante implica bajar a San Pedro o a Marbella. Penélope, mi hija pequeña, todavía no conduce. Para sus actividades, sus amigas, el médico de cabecera, cualquier cosa que se salga del pueblo, necesitaría que alguien la llevara y la trajera. Eso, multiplicado por los años que le quedan en casa, es mucho coche y muchas horas de espera.
El tercero es la oferta de servicios. En el pueblo hay lo básico, pero no hay un instituto público dentro del término municipal pegado al núcleo urbano, y los centros de salud tienen un alcance limitado. Para urgencias o especialistas hay que bajar a la costa. Eso a una edad no importa; con tres hijas a las que has acompañado a urgencias unas cuantas veces, sí.
Y hay un cuarto, más sutil: el pueblo es pequeño y eso, que en lo bueno te da tranquilidad, en lo malo te da un techo social bajo. Para alguien tímido como yo no es un problema en sí mismo, pero para una adolescente sí lo puede ser. El mundo de Penélope no cabe en treinta calles.
## Para una familia como la nuestra
Si fuéramos solo Penélope y yo, sin dos hijas mayores que vienen los domingos a comer, y si yo estuviera ya en la última fase de la crianza, Benahavís sería una opción muy seria. Es el sitio que escoges cuando ya no necesitas que el día a día sea cómodo y empiezas a valorar otras cosas: el silencio, el aire, la mesa, el paisaje.
Pero todavía no estoy ahí. Penélope tiene una vida que empieza a ser suya, las mayores aparecen sin avisar, y yo trabajo desde casa pero sigo necesitando bajar a la costa para muchas cosas. Mudarme a Benahavís ahora sería adelantarme a una etapa de la vida en la que aún no estoy, y convertir el coche en una extensión de la casa.
## Veredicto provisional
Benahavís me parece uno de los sitios más bonitos para vivir en toda la provincia, y probablemente acabe ahí algún día. Pero no es lo que la familia necesita en este momento. Lo dejo en la lista, pero más abajo de donde lo había puesto cuando empecé a escribir este post.
---
# Fuck you money VIII: antifragilidad y reservas para los malos años
URL: https://javiervalencia.net/post/fuck-you-money-antifragilidad-reservas
Llegamos al último pilar de la serie sobre [fuck you money según Joan Tubau](/post/fuck-you-money-joan-tubau), y es probablemente el más importante a largo plazo. Porque los siete anteriores asumen un mundo razonablemente estable. Y el mundo, históricamente, ha sido cualquier cosa menos estable.
Este pilar va de cómo construir tus finanzas para que las crisis —que llegarán— no solo no te hundan, sino que **te dejen en mejor posición** que antes de empezar.
## Frágil, robusto, antifrágil
El concepto es de Nassim Taleb y Tubau lo cita constantemente. Tres categorías:
- **Frágil**: lo que se rompe con el estrés. Una copa de cristal. Un sistema financiero apalancado al máximo. Una vida sin colchón.
- **Robusto**: lo que aguanta el estrés sin romperse pero sin mejorar tampoco. Una piedra. Una cuenta corriente con la nómina justa.
- **Antifrágil**: lo que **mejora con el estrés**. Los músculos al levantar peso. Un sistema inmunológico al exponerse a patógenos. **Una cartera con reservas líquidas en una crisis bursátil**.
La mayoría de la gente, en finanzas personales, aspira a la robustez: «que la crisis no me hunda». Tubau aspira a algo más alto: **que la crisis sea una oportunidad de compra**.
## Por qué las crisis enriquecen a quien tiene reservas
Las recesiones siguen un patrón muy parecido cada vez:
1. El mercado cae 30-50%.
2. Despidos en cadena. Quien tiene deuda alta y poco colchón se ve forzado a vender activos para sobrevivir.
3. Los precios de activos (acciones, pisos, negocios) bajan **porque hay vendedores forzados**, no porque los activos en sí valgan menos.
4. Quien **no necesita vender y tiene liquidez**, compra a precios deprimidos.
5. La recuperación llega (siempre llega, antes o después). Los que compraron en mínimos multiplican. Los que vendieron en pánico se quedan fuera.
Este patrón se ha repetido en 1929, 1973, 2000, 2008 y 2020. Y se va a repetir. La pregunta no es **si** habrá otra crisis, sino **cuándo** y **en qué lado estarás**.
El *fuck you money* bien construido te pone en el lado correcto:
- Tienes 6-12 meses de gastos en líquido, así que **no te despides en pánico** si pierdes el trabajo.
- Sigues aportando a fondos indexados aunque el mercado caiga: **compras barato sin pensarlo**.
- Si la oportunidad es muy grande (un piso a precio de derribo, una participación en un negocio), tienes munición disponible.
La gente que sale fortalecida de las crisis no es la más lista. Es **la que tenía reservas y mantuvo la calma**. Suelen ser cosas que se preparan en los buenos años, no en los malos.
## Las reservas en serio
Tubau distingue varios tipos de reservas, en orden de prioridad:
**1. Reserva de emergencia (3-6 meses de gastos)**
Líquida absoluta: cuenta remunerada, depósito a un día, fondo monetario. No mira la rentabilidad, mira la disponibilidad. Cubre lo predecible: paro temporal, derramas, averías mayores, problemas médicos no graves.
**2. Reserva extendida (6-18 meses de gastos)**
Semilíquida: renta fija corta, depósitos a 6-12 meses, fondos de bonos con baja volatilidad. Cubre lo menos predecible: paro largo, baja médica seria, reorientación profesional.
**3. Reserva de oportunidad (variable)**
Liquidez adicional que mantienes deliberadamente fuera de la inversión a largo plazo. **Sirve para entrar en los mínimos del mercado, no para gastos**. No todo el mundo la necesita; solo si tu cartera está madura y quieres optimizar el efecto antifrágil.
**4. Inversión a largo plazo**
Renta variable indexada, fondos diversificados. Aquí va el grueso del patrimonio una vez las reservas anteriores están en su sitio. Esta sí baja en crisis, pero **no la tocas** porque las anteriores te cubren.
El orden importa. Mucha gente invierte agresivamente sin tener la reserva 1 cubierta, y a la primera crisis se ven forzados a vender en mínimos. Eso es **destruir el efecto compuesto a propósito**.
## La deuda como anti-reserva
Las reservas líquidas son una palanca a tu favor. La deuda es exactamente la opuesta: **una palanca en tu contra durante las crisis**.
Cuanta más deuda tienes, más frágil eres. Por dos vías:
1. Cada euro pendiente exige un pago mensual fijo. Si tus ingresos bajan, los pagos no.
2. En crisis, los tipos pueden subir (como en 2022-2023) y multiplicar el coste de la deuda variable.
Tubau es muy claro: **antes de invertir agresivamente, mata la deuda mala**. Mala = consumo, tarjetas, préstamos personales, financiación de coches a tipos altos. La buena, la matizable, es la hipoteca a tipo razonable si no te ahoga.
Mi regla personal, que se solapa con la de Tubau: **el coste mensual de toda la deuda nunca debería superar el 35% del neto del hogar**. Por encima de eso ya eres frágil estructuralmente, aunque las cifras del Excel cuadren.
## Antifragilidad psicológica
Las reservas financieras solo funcionan si el comportamiento que las sostiene también es antifrágil. Tres principios prácticos:
**1. No mires la cartera más de necesario**
En una caída del 30%, el inversor que mira la cartera todos los días vende. El que la mira cada tres meses, no. **El mejor seguro contra el pánico es no ver el incendio mientras dura**.
**2. Tener un plan escrito**
Antes de que llegue la crisis, escribe en un papel:
> «En la próxima caída del mercado, voy a:
> - No vender nada.
> - Mantener las aportaciones automáticas mensuales.
> - Si las reservas siguen completas, considerar aportación extra cuando el índice esté un 30% bajo máximos.
> - No leer noticias financieras más de una vez por semana.»
En medio de la crisis no decides. **Ejecutas el plan escrito antes**. Es la única forma de evitar el cerebro de mono que llevamos dentro.
**3. Cultivar fuentes de ingreso secundarias**
Un sueldo único es frágil. Si pierdes el empleo, los ingresos caen al 70% (paro) o al 0% (autónomo). Diversificar ingresos —segundo proyecto, alquiler, royalties pequeños, lo que sea— no es solo más dinero. Es **redundancia estructural** que aguanta golpes.
Yo, por ejemplo, mantengo este blog. No paga las facturas. Probablemente no las pague nunca. Pero **diversifica mi identidad profesional**, me obliga a aprender, y construye una opción remota de monetización futura si mi puesto principal se cae. No cuento con ella; sí la tengo.
## El cisne negro y la humildad
Una nota final, que es un poco el resumen de toda la serie. La humildad ante lo que no se puede prever.
Los modelos financieros asumen distribuciones razonables. Las crisis verdaderas —pandemias, guerras, colapsos sistémicos— rompen los modelos. En 2020 nadie tenía «pandemia global» en su Excel. En 2022 nadie esperaba que bonos y bolsa cayeran a la vez.
La respuesta a esto no es predecir mejor. Es **diseñar para no predecir**:
- Mantén reservas más grandes de lo «matemáticamente óptimo».
- Diversifica más de lo que el modelo dice.
- Vive más por debajo de tus posibilidades de lo que crees necesario.
- Asume que vas a equivocarte y blinda para que el error no sea letal.
Esa es la diferencia entre robustez y antifragilidad. El robusto sobrevive. El antifrágil **se beneficia de lo inesperado**. Y solo se llega a antifrágil sobre-diseñando los márgenes en los buenos años.
## Cierre de la serie
Hemos recorrido los ocho pilares del *fuck you money* tal como yo entiendo la lectura de Joan Tubau:
1. [Vivir por debajo de tus posibilidades](/post/fuck-you-money-vivir-por-debajo-de-tus-posibilidades)
2. [Ahorro agresivo y tasa de ahorro](/post/fuck-you-money-ahorro-agresivo-tasa-de-ahorro)
3. [Inversión pasiva en fondos indexados de bajo coste](/post/fuck-you-money-inversion-pasiva-fondos-indexados)
4. [Diversificación de verdad](/post/fuck-you-money-diversificacion)
5. [Evitar la inflación de estilo de vida](/post/fuck-you-money-inflacion-estilo-de-vida)
6. [Tiempo > dinero](/post/fuck-you-money-tiempo-vs-dinero)
7. [Optionality, el valor de poder decir «que te jodan»](/post/fuck-you-money-optionality-libertad)
8. Antifragilidad y reservas para los malos años (este).
Ninguno de los ocho es revolucionario. Casi todos están en Bogle, en Mr. Money Mustache, en Taleb, en Kahneman. Lo que Tubau hace bien es **integrarlos en un marco coherente y adaptarlos al contexto español**. Y, sobre todo, ponerles un objetivo emocional comprensible: la libertad de poder decir que no.
Lo más curioso de aplicar esto en serio: **el dinero deja de ser el protagonista**. Pasa a ser una infraestructura silenciosa que permite que el verdadero protagonista —tu tiempo, tu salud, tus relaciones, tu trabajo elegido— ocupe el centro.
No es asesoramiento financiero. No es una promesa. No es magia. Es disciplina aplicada año a año, decisión a decisión, en una dirección que casi nadie cuestiona pero casi nadie sigue.
Como con casi todo lo que vale la pena: lo difícil no es entenderlo, es hacerlo.
---
# Rails sostenible (II): arranca tu app con buen pie
URL: https://javiervalencia.net/post/rails-sostenible-ii-arrancar-con-buen-pie
*Segunda entrega de la serie **[Rails sostenible](/search?tag=rails-sostenible)**, basada en **Sustainable Web Development with Ruby on Rails** de David Bryant Copeland. Si te perdiste el arranque, empieza por [la primera entrega](/post/rails-sostenible-i-sostenibilidad-y-arquitectura). Tiempo de lectura estimado: 13 minutos.*
En el [post anterior](/post/rails-sostenible-i-sostenibilidad-y-arquitectura) vimos que la sostenibilidad es la capacidad de seguir cambiando el software a coste constante. Pues bien: el primer coste que se dispara en cualquier proyecto mal arrancado es el de **poner a alguien a trabajar en él**. Copeland le dedica un capítulo entero a algo que la mayoría de equipos descuida —el setup del entorno— porque es el primer sitio donde la insostenibilidad muerde.
La tesis del capítulo es simple: un desarrollador nuevo debería poder clonar el repositorio y tener la aplicación corriendo con **un solo comando**, sin un documento de veinte pasos en Notion que nadie ha actualizado desde 2023.
## Crear la app pensando en producción
Copeland empieza por `rails new`, pero con criterio. Recomienda usar PostgreSQL desde el principio en lugar de SQLite, para que el entorno de desarrollo se parezca a producción (evitar el clásico "en mi máquina funciona" por diferencias de base de datos):
```bash
rails new mi_app --database=postgresql
```
El principio detrás es el de **paridad dev/prod** del manifiesto [12-factor](https://12factor.net/es/): cuanto más se parezcan tus entornos, menos sorpresas en el despliegue. No tiene sentido desarrollar sobre SQLite y rezar para que las queries funcionen igual en el Postgres de producción.
## Configuración por el entorno, no por el código
Todo lo que varía entre entornos —credenciales, URLs de servicios externos, claves de API— va en **variables de entorno**, nunca hardcodeado ni commiteado. Esto es de nuevo 12-factor, y Rails lo soporta de forma natural con `ENV`:
```ruby
# config/initializers/stripe.rb
Stripe.api_key = ENV.fetch("STRIPE_API_KEY")
```
Fíjate en el `fetch` en lugar de `ENV["..."]`. Con `fetch`, si la variable no existe, la aplicación **peta al arrancar** con un error claro, en vez de fallar misteriosamente en runtime cuando ya es tarde. Es un patrón pequeño con un gran retorno: convierte un fallo silencioso en uno ruidoso y temprano.
### dotenv para el desarrollo local
Escribir `STRIPE_API_KEY=... rails server` a mano cada vez es insostenible. Copeland recomienda la gema [`dotenv`](https://github.com/bkeepers/dotenv) para cargar variables desde ficheros `.env` en desarrollo y test:
```bash
# .env.development (este SÍ se commitea: valores de ejemplo, no secretos reales)
STRIPE_API_KEY=sk_test_xxxxxxxx
DATABASE_URL=postgres://localhost/mi_app_development
```
La regla es: `.env.development` y `.env.test` con valores de ejemplo van al repositorio para documentar *qué* variables hacen falta; los secretos reales van en ficheros ignorados por git (`.env*.local`) o en el gestor de secretos del entorno de producción.

## bin/setup: un comando para gobernarlos a todos
Aquí está la joya del capítulo. Rails genera un `bin/setup` por defecto, pero Copeland lo reescribe para convertirlo en un script **idempotente, ruidoso y a prueba de fallos**. La idea: ejecutarlo en una máquina recién clonada deja la app lista; ejecutarlo de nuevo no rompe nada.
```ruby
#!/usr/bin/env ruby
require "fileutils"
APP_ROOT = File.expand_path("..", __dir__)
def system!(*args)
system(*args, exception: true) # peta si el comando falla
end
FileUtils.chdir(APP_ROOT) do
puts "== Instalando dependencias =="
system!("bundle check") || system!("bundle install")
puts "\n== Preparando la base de datos =="
system!("bin/rails db:prepare") # crea/migra solo si hace falta
puts "\n== Limpiando logs y temporales =="
system!("bin/rails log:clear tmp:clear")
puts "\n== Listo. Arranca con: bin/run =="
end
```
Tres detalles que importan:
- **`system!` con `exception: true`** hace que el script se detenga en el primer error en vez de seguir adelante dejando un entorno a medias.
- **`bundle check || bundle install`** evita reinstalar gemas si ya están: idempotencia.
- **`db:prepare`** crea la base de datos si no existe y la migra si hace falta, sin quejarse si ya estaba. Es la pieza que hace el script repetible.
> Si tu README tiene una sección "Cómo montar el entorno" con más de dos líneas, esa sección es un bug. Debería poner: `git clone ... && bin/setup`.
## bin/run para levantar todo a la vez
Una app real no es solo el servidor web: hay un proceso de assets (esbuild, tailwind), quizá un worker de jobs, quizá un proceso de CSS. Pedirle a alguien que abra cuatro terminales es frágil. Copeland usa un `Procfile.dev` y una herramienta tipo [`foreman`](https://github.com/ddollar/foreman) u `overmind` para levantar todo con un comando:
```
# Procfile.dev
web: bin/rails server -p 3000
css: bin/rails tailwindcss:watch
worker: bundle exec sidekiq
```
```bash
# bin/run
#!/usr/bin/env bash
exec foreman start -f Procfile.dev "$@"
```
Rails 7 ya genera algo parecido con `bin/dev`; la idea de Copeland es la misma, anterior a que fuera la convención: **un comando levanta el entorno completo**.
## bin/ci: la misma puerta de calidad en local y en CI
Esta es otra idea que me parece de oro. En vez de tener la configuración de los checks dispersa en un YAML de GitHub Actions que solo se ejecuta en la nube, Copeland mete *toda* la verificación de calidad en un script versionado, `bin/ci`, que ejecutas igual en tu máquina que en el servidor de integración:
```bash
#!/usr/bin/env bash
set -e # aborta al primer fallo
echo "== Tests =="
bin/rails test test:system
echo "== Análisis de seguridad (Brakeman) =="
bundle exec brakeman --quiet --no-pager
echo "== Vulnerabilidades en dependencias =="
bundle exec bundler-audit check --update
echo "== Linter (RuboCop) =="
bundle exec rubocop
echo "== CI OK =="
```
El pipeline de CI se reduce entonces a una línea: `bin/ci`. Las ventajas de sostenibilidad son enormes:
- **No hay deriva** entre lo que comprueba tu máquina y lo que comprueba el servidor.
- **Puedes reproducir un fallo de CI en local** al instante, sin hacer push a ciegas.
- La configuración vive **en el repo**, versionada, no atrapada en la UI de una herramienta concreta.
Copeland incluye en la puerta de calidad dos cosas que mucha gente olvida: [Brakeman](https://brakemanscanner.org/) (análisis estático de seguridad para Rails) y [bundler-audit](https://github.com/rubysec/bundler-audit) (avisos de CVE en tus gemas). Seguridad y dependencias revisadas en cada commit, gratis.
## Logging de producción con lograge
El logger por defecto de Rails escribe varias líneas por petición, en un formato pensado para leerse en una terminal de desarrollo. En producción eso es un desastre para cualquier sistema de observabilidad: imposible de parsear, imposible de agregar. Copeland recomienda [`lograge`](https://github.com/roidrage/lograge), que colapsa cada petición en **una sola línea estructurada**:
```ruby
# config/environments/production.rb
config.lograge.enabled = true
config.lograge.formatter = Lograge::Formatters::Json.new
config.lograge.custom_options = lambda do |event|
{ request_id: event.payload[:request_id], host: event.payload[:host] }
end
```
De varias líneas ruidosas por request pasas a un JSON por línea con método, ruta, status, duración y los campos que tú añadas. Esto enlaza directamente con el capítulo de operaciones que veremos en la última entrega: sin buenos logs no hay observabilidad, y sin observabilidad mantener algo en producción es navegar a ciegas.
## Docker, pero para los servicios
El libro dedica un apéndice a Docker, y la postura de Copeland es matizada y muy sensata. Docker es excelente para reproducir los **servicios de apoyo** —PostgreSQL, Redis— de forma idéntica en todas las máquinas. Un `docker-compose.yml` para levantar la base de datos es puro carrying cost negativo: ahorras a cada persona instalar y configurar Postgres a mano.
```yaml
# docker-compose.yml
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: postgres
ports: ["5432:5432"]
volumes: ["pgdata:/var/lib/postgresql/data"]
redis:
image: redis:7
ports: ["6379:6379"]
volumes:
pgdata:
```
Ahora bien, **dockerizar todo el ciclo de desarrollo** —correr el propio Rails dentro de un contenedor, con el código montado por volumen— es otra historia. Tiene un carrying cost real: lentitud de I/O en algunos sistemas, complejidad de debugging, otra capa que entender. Copeland no lo prohíbe, pero te invita a sopesarlo: para muchos equipos, Ruby instalado en local + servicios en Docker es el punto óptimo.
## Mi versión
El `bin/ci` me cambió la forma de trabajar. Antes vivía con la angustia del "a ver si pasa CI" después de cada push. Tener un único script que ejecuta lo mismo en local me devolvió el control: si `bin/ci` pasa en mi máquina, pasa en el servidor, punto. Lo replico incluso fuera de Rails —en mis proyectos Go tengo un `bin/ci` equivalente que corre `go test`, `golangci-lint` y `govulncheck`.
Sobre `bin/setup`: la prueba de fuego que hago es borrar todo y clonar el repo en una carpeta limpia. Si `bin/setup` no me deja la app corriendo, está roto, por mucho que "en mi máquina ya funcione". Es la mejor inversión anti-insostenibilidad que conozco, porque el coste de un onboarding malo se paga con cada persona nueva.
Y lo de `ENV.fetch` en vez de `ENV[]` parece una tontería hasta que te ahorra una tarde de debugging persiguiendo un `nil` que se propagó tres capas hacia abajo. Fallar pronto y ruidosamente es, casi siempre, lo sostenible.
## Lo que viene
Con el entorno reproducible y la puerta de calidad montada, ya podemos escribir código sin miedo. En el **próximo post** entramos en el corazón de todo el libro y de esta serie: por qué **la lógica de negocio no va en los Active Records**, qué es el *seam* del que habla Copeland, y cómo darle a esa lógica una casa propia mediante servicios *stateless* con nombres explícitos.
---
# Fuck you money VII: optionality, el valor de poder decir 'que te jodan'
URL: https://javiervalencia.net/post/fuck-you-money-optionality-libertad
Llegamos al pilar que da nombre al concepto entero. Si en los seis anteriores hemos hablado de cómo construir un colchón, ahora toca hablar de **para qué sirve**. Y según Tubau, no sirve para comprarse un Tesla. No sirve para retirarse a los 45. No sirve para sentirse rico. Sirve para una cosa concreta y muy específica: **tener la opción de decir que no**.
Esa opción tiene nombre técnico: **optionality**.
## Qué es optionality
El término viene de la teoría financiera de opciones. Una opción es un contrato que te da el **derecho, pero no la obligación**, de hacer algo. Comprar una acción a 100 €. Vender un piso a 250.000 €. Salir de un contrato a los seis meses.
Lo importante de una opción es que **vale dinero aunque nunca la ejerzas**. El simple hecho de poder elegir cuándo y cómo actuar tiene valor económico. En los mercados financieros eso lo cuantifica Black-Scholes; en la vida real lo cuantifica tu sueño, tu salud mental y tus decisiones de carrera.
Tubau, siguiendo a Taleb, defiende que **el dinero acumulado es una opción**. No te obliga a usarlo. Te permite, si quieres, ejercerlo. Y por el mero hecho de existir, cambia cada decisión que tomas.
## Cómo cambia tu vida sin gastarlo
Esto es lo más contraintuitivo del pilar: **el dinero del *fuck you money* funciona aunque no lo gastes**. Funciona por su existencia, no por su uso.
Algunos ejemplos concretos de cómo cambia la vida sin moverse del banco:
**En negociaciones salariales**: si sabes que puedes irte sin nómina durante 18 meses, negocias diferente. No estás «pidiendo». Estás **evaluando**. Y eso se nota en cómo te trata el otro lado.
**Con clientes tóxicos** (autónomos): aceptas o rechazas en función de si el proyecto te interesa, no de si te llega la siguiente factura. Y los clientes tóxicos lo huelen. Y se van solos a buscar a otro.
**Con un jefe difícil**: en lugar de tragarte cosas que no deberías, las dices. Si la respuesta es razonable, mejora el ambiente. Si no, sabes que tienes salida. Lo paradójico es que cuando puedes irte, **muchas veces ya no necesitas irte**, porque dejas de aguantar lo que destruía la relación.
**En decisiones de salud**: si el médico te dice que tienes que parar tres meses por una operación, paras. Sin estrés. Sin tragar pastillas para volver al trabajo el día 12.
**En decisiones familiares**: si tu pareja se queda sin trabajo, no es una catástrofe. Si tu hijo necesita un cambio de colegio caro, lo evalúas con calma. Si un padre se pone enfermo, puedes acompañarlo.
**En la sensación general**: duermes mejor. Es difícil de cuantificar y es el efecto más grande.
## La opción que más vale: la salida
De todas las opciones que el *fuck you money* te da, Tubau pone en primer lugar **la opción de irte**. Es la más poderosa porque es la que más temen los que ejercen poder sobre ti.
Cuando tu jefe sabe que **no puedes irte** —porque tienes hipoteca, dos hijos, ningún colchón y un mercado laboral malo— tiene una palanca enorme sobre ti. No tiene que ser un mal jefe para usarla. Basta con que el sistema la tenga disponible.
Cuando tu jefe sabe que **puedes irte tranquilamente**, esa palanca desaparece. Y se nota:
- No te asignan los proyectos basura por defecto.
- Te incluyen en las conversaciones importantes.
- Te respetan los horarios.
- Te suben más rápido.
Esto es el mecanismo que J.P. Morgan resumía cuando supuestamente dijo que «un hombre con dinero en el banco trabaja con la cabeza erguida». No es solipsismo: es la economía básica del poder de negociación.
## Las opciones que no contemplabas
Hay otro nivel de optionality más sutil, y es el que más me ha sorprendido a mí desde que voy aplicando esta filosofía. **El dinero ahorrado te permite ver opciones que antes ni veías**.
Cuando vives al límite, tu campo mental de decisiones es estrecho:
- ¿Voy al curro mañana o no?
- ¿Le digo que sí al cliente difícil?
- ¿Acepto la oferta o la rechazo?
Cuando tienes colchón, el campo se ensancha:
- ¿Y si me tomo seis meses sabáticos?
- ¿Y si me especializo en otra cosa?
- ¿Y si monto algo por mi cuenta?
- ¿Y si nos mudamos a un sitio más barato y trabajamos menos?
- ¿Y si financio yo mi propio proyecto en lugar de pedirle dinero a un inversor?
Estas opciones siempre estuvieron ahí. Pero **no las veías** porque tu cerebro filtraba todo lo que no fuera supervivencia. Tener dinero te abre el filtro. Y entonces empiezas a tomar decisiones que la versión sin colchón de ti ni hubiera contemplado.
Tubau lo formula bonito: «*el dinero no compra felicidad, pero compra opciones. Y la felicidad casi siempre vive dentro de una opción que antes no podías ejercer*».
## El umbral de la libertad útil
¿Cuánto hace falta para tener optionality real? La respuesta corta: **menos de lo que crees, más de lo que tienes**.
Tres umbrales que Tubau menciona, con sus implicaciones:
**1. Colchón básico (6 meses de gastos)**
Permite encajar despidos sin desastre. Permite rechazar un cliente. Es **el suelo absoluto**. Sin esto, no tienes optionality, tienes precariedad camuflada.
**2. Año sabático (12-18 meses de gastos)**
Permite irte de un trabajo malo sin tener uno nuevo. Permite formarte en algo distinto. Permite intentar montar tu cosa y fallar sin arruinarte. Aquí entra la **optionality activa**: ya no solo te proteges, también puedes mover ficha.
**3. Independencia parcial (5-10 años de gastos)**
Permite reducir jornada, cambiar de sector, vivir de tu trabajo solo parcialmente. **La optionality empieza a ser estructural**: ya no son ventanas puntuales, es un nuevo régimen de vida.
**4. Independencia total (25+ años de gastos, la regla del 4%)**
Permite no volver a trabajar nunca por dinero. Pocos llegan. Y muchos de los que llegan **siguen trabajando**, porque a esa altura el trabajo se elige por gusto, no por necesidad. Es la **optionality llevada a su consecuencia natural**: trabajas porque quieres, no porque tienes que.
El segundo umbral —entre 12 y 18 meses de gastos— es donde **la optionality empieza a cambiar la vida**. Y es alcanzable en pocos años si los pilares anteriores se aplican.
## La trampa de gastar la opción antes de tenerla
Un error muy común que merece advertencia. Mucha gente, al ver que ha llegado al umbral 1 (6 meses de gastos), siente que «ya tiene optionality» y se relaja. Empieza a gastar más, sube el estilo de vida, asume nuevos compromisos.
El problema es que la optionality solo existe **mientras el ratio reserva/gastos se mantiene**. Si has metido 18.000 € de colchón con gastos de 3.000 €/mes (6 meses) y subes los gastos a 4.500 €/mes, **ya no tienes 6 meses, tienes 4**. La opción se ha encogido aunque la cifra absoluta no haya cambiado.
Tubau insiste en mirar siempre **el ratio**, no el saldo. Y cuando ese ratio baja por gasto, **no por uso**, has perdido optionality sin haber ejercido ninguna opción. El peor escenario.
## La opción más rara: quedarte
Hay una última forma de optionality que casi nadie cuenta y a mí me parece la más interesante. **La libertad de quedarte**.
Mucha gente aguanta su trabajo «porque toca». Lo aceptan, lo sufren, lo trabajan. Si les preguntas «¿te quedarías si no necesitaras el dinero?», dicen que no.
Cuando esa misma gente llega a tener *fuck you money* y se pregunta seriamente la pregunta, descubren algo curioso: **muchos se quedan**. No porque tengan que. Porque cuando ya no es una obligación, la dimensión positiva del trabajo —los compañeros, el aprendizaje, el sentido— se hace visible.
La diferencia entre **aguantar el trabajo** y **elegir quedarte en el mismo trabajo** es enorme aunque externamente sean idénticos. Una destruye. La otra construye. Y solo se accede a la segunda a través de la optionality.
Eso, para mí, es lo más importante de la filosofía completa. No la posibilidad teórica de mandar todo a la mierda. Sino **la posibilidad real de elegir todo lo que ya estás haciendo**.
En el último post de la serie cerramos con el octavo pilar: **antifragilidad y reservas para los malos años**, donde montamos las defensas que hacen que toda esta arquitectura sobreviva a las crisis inevitables.
---
# Las diez mejores películas de mi vida: 10. Doce hombres sin piedad
URL: https://javiervalencia.net/post/top-10-peliculas-10-doce-hombres-sin-piedad
Llegamos al final. La décima de mi top diez. La he reservado para el cierre porque **es la película que mejor representa**, en mi cabeza, **lo que el cine puede hacer con muy poco**: un escenario, un puñado de actores, un guion concentrado y un par de horas. Es ***Doce hombres sin piedad***. Y no la he puesto la décima porque sea la peor de la lista. Probablemente, en términos de pura arquitectura de guion, **es la mejor de todas**. Está la décima porque **es la que menos veces he revisitado**, y eso **es el criterio de mi lista personal**: cuántas veces vuelvo a una película mide cuánto me llega, no cuánto la admiro.
Esta es la décima entrega. Las nueve anteriores están publicadas: la [uno](/post/top-10-peliculas-1-cinema-paradiso), la [dos](/post/top-10-peliculas-2-cadena-perpetua), la [tres](/post/top-10-peliculas-3-el-padrino), la [cuatro](/post/top-10-peliculas-4-casablanca), la [cinco](/post/top-10-peliculas-5-la-vida-es-bella), la [seis](/post/top-10-peliculas-6-forrest-gump), la [siete](/post/top-10-peliculas-7-la-lista-de-schindler), la [ocho](/post/top-10-peliculas-8-erase-una-vez-en-america) y la [nueve](/post/top-10-peliculas-9-amelie). Si entras frío, recomendable empezar por la uno.
## La obra maestra hecha con casi nada

*Doce hombres sin piedad* es una película de **1957**, dirigida por **Sidney Lumet** (su debut como director, después de venir de la televisión, donde había dirigido casi cien obras de teatro emitidas en directo durante los años cincuenta). Está basada en una **obra de teatro de Reginald Rose** del año anterior, escrita originalmente para televisión y adaptada por el propio Rose para la película. Se rodó en **menos de tres semanas** con un presupuesto **bajísimo**.
El argumento, de **una simplicidad extrema**: un jurado de doce hombres tiene que decidir, por unanimidad, si declara culpable o no de asesinato a un chico de los suburbios acusado de matar a su padre. Toda la película transcurre en **una sola sala**: la sala de deliberación del jurado. Empezamos con once hombres convencidos de que el chico es culpable y un solo hombre (el jurado número 8, interpretado por **Henry Fonda**) que no está seguro y propone deliberar. **El resto de la película es la deliberación**: los argumentos, las dudas, los conflictos, las personalidades, los prejuicios, los reveses argumentales. Y, al final, **el cambio**.
Eso es todo. Una habitación. Doce hombres. Una mesa. Un ventilador roto. Un cuarto de baño anexo. Una jarra de agua. Y, fuera, un día caluroso de verano que se va convirtiendo en tormenta a medida que avanza la película.
**Y con eso solo, Lumet construye una de las grandes películas del siglo XX**.
## Lo que la película demuestra sobre el cine
Hay **una pregunta fundamental** que muchos estudiantes de cine se hacen al empezar la carrera: ¿qué es lo que hace que algo sea cine y no teatro filmado? Es una pregunta clásica y difícil. Si me la haces a mí, **te respondo proyectando *Doce hombres sin piedad***.
Porque, en la superficie, esta película **podría ser teatro filmado**: una sola habitación, casi todo diálogo, sin acción exterior, con personajes que no se mueven mucho. Y, sin embargo, **es cine en estado puro**, **no es teatro**. La diferencia está en cómo Lumet **usa la cámara como personaje**.
Lumet hace **una decisión técnica brillante**: la película **cambia de lentes** progresivamente. Empieza con **lentes gran angular** (con las que la habitación se ve amplia, los personajes están distantes entre sí, hay aire alrededor). A medida que avanza la película, **va usando lentes con focales más largas** (la habitación parece comprimirse, los personajes se acercan visualmente, el aire desaparece). Y, en las escenas finales, **usa lentes muy largas** (los personajes parecen amontonarse, la habitación se siente claustrofóbica, sudorosa, pequeñísima).
**Eso es cine, no teatro**. El espectador percibe ese estrechamiento del espacio **a un nivel subconsciente**: la película va aumentando la presión sobre los jurados sin que estos cambien físicamente de sitio. Es **el equivalente cinematográfico de subir el termostato**: nadie se da cuenta del proceso, pero al final está sudando.
Y luego están **los planos**. Lumet **alterna entre planos generales** (que muestran la mesa entera, los doce jurados a la vez) **y planos cerrados** (que aíslan a un jurado en una decisión clave). El paso entre los dos tipos de plano **define el ritmo emocional** de la película. Cuando el guion está en modo "discusión colectiva", plano general. Cuando el guion está en modo "fulanito tiene que decidir algo", plano cerrado.
Y luego están **las decisiones de iluminación**: la luz natural a través de las ventanas en la primera mitad, la luz artificial cuando empieza a llover, los reflejos en los rostros sudorosos cuando el ventilador no funciona y el calor aprieta. Cada **uno de esos detalles físicos** está al servicio de la deliberación intelectual.
Por eso Lumet, con esta sola película, **se ganó el respeto de toda la profesión** y se convirtió en uno de los grandes directores americanos del siglo XX (su filmografía posterior incluye *Tarde de perros*, *Network*, *Veredicto final*, *El príncipe de la ciudad*, todas notables).
## Henry Fonda y la decencia

Henry Fonda, en 1957, era **un actor con una carrera de veinte años ya consolidada**: *Las uvas de la ira*, *Pasión de los fuertes*, *El fugitivo*. Era **el actor americano de la decencia**, especializado en hombres tranquilos, justos, lentos para la violencia, firmes para la conciencia.
En *Doce hombres sin piedad*, Fonda **es el productor además del protagonista**. Compró los derechos de la obra de teatro y financió la película (junto con la productora). Esa apuesta personal se nota en **la dedicación** que pone en el papel del jurado número 8.
El jurado número 8 **es un personaje difícil**: si lo presentas como un héroe seguro de sí, parece **un fanático**. Si lo presentas como demasiado cauto, parece **un cobarde**. Lo que hace Fonda es **construir un personaje que duda en voz alta**. Su primera intervención no es "*estoy seguro de que es inocente*". Es "*no estoy seguro de que sea culpable*". Esa diferencia, **mínima en apariencia**, es **enorme en términos de carácter**: el jurado 8 no defiende una posición; **defiende un proceso**. Defiende **la idea de que, antes de condenar a un chico a la silla eléctrica, conviene asegurarse**. Y eso es **una posición moral muy diferente** de "*yo creo que es inocente*".
Eso es lo que hace que Fonda en este papel sea memorable. No es **el héroe** que tiene la verdad. Es **el ciudadano que pide examen**. Y, a lo largo de la película, va arrastrando uno a uno a los demás jurados, **no convenciéndolos de la inocencia del chico** (esa cuestión queda abierta, como en la vida real), sino **convenciéndolos de que hay duda razonable**, lo cual es exactamente lo que un sistema judicial requiere.
Hay una escena que me parece **una de las más potentes** del cine americano: Fonda, en mitad de la película, propone que **se haga una votación secreta**. Si todos siguen pensando "culpable", él se rendirá y votará culpable también. Si alguien cambia, **se sigue deliberando**. La votación se hace. Se cuentan las papeletas. Diez votos por culpable. **Una en blanco**. Alguien ha cambiado.
Esa **una en blanco** es **el punto de inflexión** de la película. Y la cara de Fonda al verla, **sin sonrisa**, sin victoria, **solo aliviada**, es **uno de los detalles más finos** de su interpretación. **No celebra**. **No hace gesto de superioridad**. **Simplemente respira**. Y la película continúa.
## Los doce hombres como microcosmos
Reginald Rose, el guionista, hizo algo brillantísimo con su elenco: **cada uno de los doce jurados representa un tipo social americano** de los años cincuenta. **Sin que la película lo subraye**, el espectador entiende que en esa habitación está **una pequeña muestra de la sociedad**:
- **Jurado 1** (Martin Balsam): el presidente del jurado, hombre formal, mediano, sin opiniones fuertes.
- **Jurado 2** (John Fiedler): el hombre tímido, banquero pequeño, dubitativo.
- **Jurado 3** (Lee J. Cobb): el padre rabioso, con conflicto con su propio hijo, **el gran antagonista**.
- **Jurado 4** (E.G. Marshall): el broker rico, frío, racional, sin emociones.
- **Jurado 5** (Jack Klugman): el hombre del barrio pobre, que ha vivido lo que vive el chico acusado.
- **Jurado 6** (Edward Binns): el obrero, decente, callado.
- **Jurado 7** (Jack Warden): el vendedor, el que tiene prisa por terminar para ir a un partido.
- **Jurado 8** (Henry Fonda): **el arquitecto, el que duda**.
- **Jurado 9** (Joseph Sweeney): **el viejo**, observador, sutil, **el primero que cambia su voto**.
- **Jurado 10** (Ed Begley): el racista, **el otro antagonista**.
- **Jurado 11** (George Voskovec): el inmigrante europeo, relojero, formal.
- **Jurado 12** (Robert Webber): el publicista, superficial, oportunista.
Cada uno de estos personajes está **construido en pocos minutos** por dos cosas: **lo que dice** y **lo que hace**. La película no se molesta en explicar quién es quién con flashbacks o monólogos. Confía en **el espectador inteligente**: cada uno se va revelando a través de **sus reacciones a los argumentos** y **sus pequeños tics**.
Y el genio del guion es que **cada uno de los cambios de voto** está **motivado por algo distinto**: uno cambia por compasión, otro por lógica, otro por presión social, otro por descubrimiento de su propio prejuicio, otro por agotamiento, otro por sentido común. Esa **diversidad de motivaciones** convierte el proceso de deliberación en **un retrato general de cómo cambia la opinión** de personas distintas.
Eso es **lo que la película enseña**: que **los seres humanos no cambian de opinión por una sola razón**. Cambian por **mil razones distintas**, cada una específica para cada persona. Y, **para hacer que un grupo cambie de opinión**, hay que **encontrar la razón específica de cada miembro**. No hay un argumento universal que valga para todos.
Esa lección, llevada al terreno cotidiano, **es de aplicación constante**: en discusiones de pareja, en discusiones familiares, en negociaciones laborales, en debates políticos. **No hay un argumento mágico**. Hay **doce conversaciones distintas** que tienes que tener si quieres convencer a doce personas distintas. La película es, **en ese sentido**, **un manual de persuasión**, **el mejor que ha producido el cine**.
## Lee J. Cobb y el conflicto interno
Quiero detenerme en el personaje del **jurado número 3**, interpretado por **Lee J. Cobb** en una de las grandes interpretaciones secundarias del cine americano. Lee J. Cobb era un actor de teatro de los grandes (creó el Willy Loman de *Muerte de un viajante* en Broadway) y su papel en *Doce hombres sin piedad* es **el contrapeso de Fonda**: el jurado más vehementemente convencido de la culpabilidad, el más resistente a cambiar de opinión, el que **al final** se convierte en **la última voz que hay que cambiar**.
Lo que hace Cobb es **revelar progresivamente** que su rabia contra el chico acusado **no es por el caso**. Es **por su propia historia**: Cobb tiene un hijo que se ha distanciado de él (vemos una foto en su cartera y la rompe en una escena), y **su rabia con el chico del juicio es proyección de su rabia con su propio hijo**. La película no lo explicita; lo deja entender. Y, cuando el jurado 3 se da cuenta de su propia proyección, **se derrumba**. Su última escena, **rota, llorando, susurrando "no es culpable"**, es **una de las grandes catarsis del cine**.
Eso es **lo que la película demuestra de manera más profunda**: que **los juicios morales que hacemos sobre los demás están casi siempre teñidos de nuestras propias historias no resueltas**. El jurado 3 no estaba juzgando al chico; estaba **juzgándose a sí mismo a través del chico**. Cuando se da cuenta, el juicio se resuelve.
Esa observación, **de aplicación continua en la vida**, está en el corazón de la película. Y, **a los cuarenta y tantos**, después de muchos conflictos vividos en familia, en trabajo, en amistades, **uno aprende a reconocer en sí mismo el patrón del jurado 3**: cuando me enfado mucho con alguien por algo concreto, **muchas veces** estoy proyectando mi propia rabia con otra cosa que no he resuelto. **Notarlo es media solución**. Y la película es **la mejor escuela** que conozco para notarlo.
## La mejor obra de guion de la historia (probablemente)
Si tuviera que elegir **una sola película para enseñar a alguien cómo se construye un guion**, sería esta. **No tiene equivalentes**.
Lo que hace Reginald Rose es **una arquitectura matemática**:
- **Acto uno**: presentación de los doce jurados, primera votación (11-1), establecimiento del problema.
- **Acto dos**: revisión de las pruebas una a una (el cuchillo, los testimonios de los testigos, la coartada del chico, la cronología, etc.). Cada prueba se discute, cada prueba se desmonta o se mantiene. Mientras se discuten, **los jurados van cambiando de voto** uno a uno.
- **Acto tres**: el último cambio (el jurado 3) y la decisión.
Y, durante esos tres actos, **cada elemento del guion tiene función**. **No hay relleno**. **No hay diálogos sobrantes**. **No hay personajes** que estén ahí para llenar tiempo. **Cada línea avanza algo**: la trama, el carácter, la relación entre dos personajes, una idea moral. **Cada uno de los noventa y cinco minutos** de la película **está justificado**.
Eso es **rarísimo**. La mayoría de películas tienen, en algún momento, una escena que se podría cortar sin perder nada. *Doce hombres sin piedad* no tiene esas escenas. **Es la película más concentrada** que he visto. La densidad por minuto **es máxima**.
Si te interesa el guion, está disponible publicado y vale la pena leerlo, **antes o después** de ver la película. Es **un diamante** de la artesanía narrativa. Aprendes más leyendo ese guion que con cinco libros teóricos.
## Lo que la película dice sobre la justicia
La película **no dice** que el chico sea inocente. La película no resuelve el caso. La película no nos cuenta **lo que pasó realmente**. Y eso es **la mejor decisión moral** del guion.
Si la película hubiera terminado con **una revelación**, en plan "*el chico era inocente, ¡qué bien que lo absolvieron!*", la película **se hubiera reducido** a una historia de héroes contra prejuicios. Pero la película **deja la duda abierta**: **no sabemos si el chico mató o no a su padre**. Lo que sabemos es **que las pruebas no son concluyentes** y que, en un sistema basado en presunción de inocencia, **eso significa absolución**.
Esa diferencia es **filosóficamente decisiva**. La película **defiende un sistema**, no **una conclusión específica**. Defiende que, **incluso cuando un acusado pueda ser culpable**, si las pruebas no son concluyentes, **debe ser absuelto**. Porque **el sistema judicial** está diseñado para minimizar errores judiciales **en una dirección concreta** (mejor liberar a un culpable que condenar a un inocente). Y esa decisión sistémica vale **incluso si en el caso concreto fallamos**.
Esa idea, **bien defendida en una película de hora y media**, es de **una sofisticación moral altísima**. Y es **la razón por la que la película sigue siendo relevante** sesenta y nueve años después de su estreno. Porque, en una época en que los discursos populistas cuestionan los fundamentos del sistema judicial (en muchos países), **esta película sigue defendiendo, mejor que cualquier panfleto, por qué tenemos esos fundamentos**.
## Lo personal: por qué me llega
He visto *Doce hombres sin piedad* solo **tres veces** en mi vida (y al revés que las anteriores películas, no es porque no me llegue, sino porque cada visión es **densísima** y necesito tiempo entre ellas para volver). La primera, en mi adolescencia, en una versión doblada en VHS, y la disfruté más como **producto de género** (juicios) que como obra mayor. La segunda, en mi treintena, en una sala de cine reestrenando clásicos, y la entendí como **obra de cine**. La tercera, hace pocos años, en versión original con subtítulos, y la entendí como **lo que es**: **un tratado fílmico sobre cómo deliberar bien**.
Y eso es **lo que me llega ahora**, a los cuarenta y muchos: que la película es **un manual** sobre cómo se delibera. **Cómo se discute en serio**. Cómo se construye una argumentación. Cómo se admiten las dudas. Cómo se identifican los prejuicios propios. Cómo se manejan los conflictos en grupo. Cómo se llega a un acuerdo en un grupo donde **todos parten de posiciones distintas**.
En mi vida profesional (DevOps, telco, ERP, VoIP, los muchos años de programar, gestionar y discutir con equipos diversos), **la mayoría de los conflictos** se han resuelto **del mismo modo** que en esta película: **uno o dos se resisten**, **el resto va cediendo poco a poco**, **al final hay que abordar al que más se resiste con paciencia y comprensión**. *Doce hombres sin piedad* es **la película de las reuniones de trabajo bien llevadas**. La que pondría a quien empieza a gestionar equipos.
Hay además **una dimensión personal** que me ha llegado especialmente con los años: **mi propia tendencia, como introvertido**, a **callarme en grupos**. La película me enseña que **callarse no siempre es virtud**. A veces, **callarse** mientras un grupo toma la decisión equivocada **es complicidad**. El jurado 8 **no es histriónico**, no se sube a las mesas, no monta espectáculo. **Pero habla**. Pide turno. Defiende su posición con educación pero con firmeza. Y, al hacerlo, salva una vida (probablemente).
Esa lección, para alguien introvertido, **es enorme**: **el deber de hablar cuando hay que hablar**, aunque no apetezca, aunque uno preferiría callarse. Yo, después de muchos años, **he ido aprendiendo a hacerlo en lo profesional**: a oponerme cuando creo que un equipo está cayendo en un error, aunque me incomode el conflicto. Y la película es, **en gran medida**, **la teoría detrás** de esa práctica.
## ¿Por qué la décima y no más arriba?
Lo escribí al principio: **la décima posición no significa que sea la peor**. Significa que es **la que menos he revisitado**.
Esta película, en términos puramente cinematográficos, **podría estar en mi top tres**. Es objetivamente **una obra mayor**. La estructura es perfecta. El guion es impecable. La dirección es magistral. La actuación de Fonda y Cobb es de las grandes. **Todo en ella es excelente**.
Pero, **a la hora de poner una película un domingo cualquiera**, **rara vez** elijo *Doce hombres sin piedad*. **Es exigente**. **Pide concentración**. **Pide deliberación mental** en paralelo a la deliberación de los personajes. No es **una película fácil de tarde de sofá**. Es **una película de día reservado** y **mente despierta**.
Por eso, en mi top diez personal, **es la décima**. Le tengo **el respeto que merece**. Pero **no la veo todos los años**. Veo más a Cinema Paradiso, más a Cadena perpetua, más a Forrest Gump. Y eso, **subjetivo y honesto**, es lo que define **mi top diez personal**.
## A quién se la recomiendo
A todo el mundo. **Sin excepciones**.
Específicamente:
- **A cualquiera que vaya a ser jurado en un juicio**, si tienes la suerte de tocarte alguna vez. Esta película es **la mejor formación**.
- **A cualquiera que gestione equipos**. Es **manual operativo**.
- **A cualquiera que viva en pareja o tenga hijos**. Es **clínica de discusiones bien llevadas**.
- **A los introvertidos**, especialmente. La lección de que **a veces hay que hablar** es decisiva.
## Cómo verla
**Versión original con subtítulos**, sin discusión. La doblada está disponible y es decente, pero el inglés americano de Fonda, Cobb, Marshall y compañía **es una parte del aroma** de la película. Las inflexiones, los acentos, las pausas. Doblado se pierde **mucho**.
**Tarde libre, mente despierta, sin distracciones**. Esta no es película para encadenar con otras. Es **una sesión única, intensa, breve** (dura noventa y cinco minutos, lo cual la hace **más corta** que casi cualquier película de mi lista, pero **igualmente exigente**).
**Si la has visto, vuélvela a ver con un cuaderno al lado**. Esta es de las pocas películas en las que **tomar notas** mejora la experiencia. Anota los argumentos, los cambios de voto, los momentos clave. Después tendrás **un mapa de cómo se delibera bien**.
## Cierre de la serie
Y aquí termina **mi top diez de películas favoritas de mi vida**. Diez películas, dieciséis días de serie, casi treinta y cinco mil palabras escritas. **Lo más largo que he intentado en este blog**.
Si has llegado hasta aquí leyendo todas las entregas, **gracias**. De verdad. Es mucha lectura, especialmente sobre un tema (cine) que no es el habitual del blog. Y si has llegado solo a algunas, también gracias: cada post está pensado para sostenerse por sí solo, además de formar parte de la serie.
Como reflexión final, **un par de cosas**.
**Primera cosa**. Una lista de diez películas favoritas **es una autobiografía**. Si miras mis diez (Cinema Paradiso, Cadena perpetua, El padrino, Casablanca, La vida es bella, Forrest Gump, La lista de Schindler, Érase una vez en América, Amélie, Doce hombres sin piedad), **estás viendo a la persona que las elige**. Mis temas obsesivos están ahí: el paso del tiempo, la paternidad, la amistad masculina, la memoria, el sacrificio, la pequeña felicidad cotidiana, la deliberación moral. Si me conoces, las habrás reconocido. Si no me conocías, ahora me conoces mejor.
**Segunda cosa**. Esta lista **va a cambiar**. Si la hago dentro de cinco años, **algunas películas se caerán y entrarán otras**. Las dos que más tiempo llevan en mi top diez son **Cinema Paradiso** y **Cadena perpetua**, y son las que estoy más seguro de que **no se moverán**. Las otras ocho **están menos firmes**: la décima, especialmente, podría ser sustituida por *El gran Lebowski*, *Pulp Fiction*, *Apocalypse Now*, *El laberinto del fauno*, *Goodfellas*, *El club de la lucha*, *Magnolia*, *El árbol de la vida*, *Eternal Sunshine of the Spotless Mind*, *Spotlight*, *In the Mood for Love*, *Lost in Translation*, o cualquiera de varias docenas más. Si te gusta alguna que no está en mi lista, **probablemente tienes razón**: tu lista es tu lista, y las listas son personales.
**Tercera y última cosa**. He disfrutado **mucho** escribiendo esta serie. Más de lo que esperaba. Las películas son **un buen tema** para un blog: te llevan a hablar de cosas profundas (la memoria, la paternidad, la muerte) sin que parezca pretensioso, porque la película te da la excusa. Si te ha gustado el formato, igual hago algo parecido en el futuro con **otros temas culturales**. Una lista de los diez mejores **discos** de mi vida, una lista de los diez mejores **libros**, una lista de las mejores **series de televisión**. Si tienes preferencia, dímelo.
Mañana volvemos al programa habitual. Probablemente PostgreSQL, Go, Costa del Sol, hijas, peña flamenca o algo similar. La pausa cinematográfica termina aquí.
Gracias por acompañarme en estas tres semanas.
Cinco minutos para el final del último post: **si después de leer todo esto solo ves una de las diez en tu vida, que sea Cinema Paradiso**. Es **mi número uno**, y lo va a seguir siendo.
Hasta el próximo post.
---
# Las diez mejores películas de mi vida: 9. Amélie
URL: https://javiervalencia.net/post/top-10-peliculas-9-amelie
A la novena posición de mi top diez llega **una película que se diferencia mucho de las ocho anteriores**. No habla del paso del tiempo. No habla del Holocausto. No habla de la mafia. No habla de prisiones. No habla de nostalgia siciliana. **Habla de las pequeñas alegrías de la vida diaria**, filmadas con una luz cálida, con una banda sonora de acordeón, en las calles de Montmartre. Es **Amélie**. Y, por mucho que sea distinta a las demás, **se gana su sitio** porque demuestra que **la felicidad cotidiana también merece cine**.
Esta es la novena entrega de la serie. Las ocho anteriores están publicadas. La serie va de la uno hacia abajo. Si entras frío, conviene leer primero la [número uno](/post/top-10-peliculas-1-cinema-paradiso). Mañana cierro con la décima.
## La película más alegre de la lista

Si haces el repaso de mi top diez, te das cuenta de que **soy un señor con cierta inclinación al drama**. *Cinema Paradiso* es nostálgica. *Cadena perpetua* es una película carcelaria. *El padrino* es operística pero oscura. *Casablanca* es romántica pero triste. *La vida es bella* termina de forma devastadora. *Forrest Gump* es agridulce. *La lista de Schindler* es **lo opuesto a alegre**. *Érase una vez en América* es elegíaca. Ocho películas que, **si pudieran hablar, conversarían entre ellas en voz baja, con la mirada baja, mientras pasa una hoja seca por debajo de la mesa**.
Y entonces llega ***Amélie***. Y de repente todo se llena de **luz dorada, color rojo intenso, verdes saturados, música de acordeón, pequeñas felicidades, recetas de creme brûlée, gnomos viajeros, postales con texturas**. Es **la nota brillante** de mi lista. Y conviene tenerla. Porque, si una lista de favoritas es sólo drama, está incompleta. **La risa también está en la vida**. Y *Amélie* es la película que mejor ha capturado, en mi opinión, **una clase específica de risa**: la que viene de mirar el mundo como si fuera asombroso.
Y eso es exactamente la película. **La protagonista mira el mundo como si fuera asombroso**, y a partir de ese asombro **decide intervenir en pequeñas vidas para hacer felices a otros**, sin pretender una recompensa más allá de **la observación discreta** de la felicidad provocada.
## Quién es Amélie y por qué decide hacer lo que hace
Para quien no haya visto la película, hago contexto rápido. **Amélie Poulain** (Audrey Tautou) es una mujer joven que vive sola en Montmartre, trabaja de camarera en un café, no tiene pareja, no tiene casi familia (su madre murió hace años, su padre vive solo y deprimido), y tiene una imaginación tan rica que **prefiere pasar la mayor parte del tiempo fantaseando**. La película comienza con un narrador en off que nos da **detalles minuciosos** de cada personaje: lo que les gusta, lo que les irrita, lo que les hace felices. Cosas como "*a Amélie le gusta meter las manos en sacos de granos, romper la costra de la creme brûlée con la cucharilla, hacer rebotar piedras en el canal Saint-Martin*". **Esos detalles son el ADN de la película**.
Un día, Amélie encuentra **una vieja caja de tesoros infantiles** detrás de una baldosa de su baño. Decide buscar al niño (ya hombre adulto) que escondió la caja décadas atrás y devolvérsela. Cuando el hombre la recibe y se emociona **al recordar su infancia**, Amélie **siente algo que no había sentido nunca**: la felicidad de haber hecho feliz a alguien. Y, a partir de ese momento, **decide que va a dedicarse a eso, en secreto**: hacer felices a las personas que la rodean, **sin que se enteren, sin que sepan que fue ella**.
A partir de esa premisa, la película es una sucesión de pequeñas intervenciones de Amélie en las vidas de **el portero del edificio, su padre, una compañera de café enamorada, un cliente del café maltratado por el dueño, una vecina viuda, un niño tímido**. Cada intervención es **un pequeño relato dentro del relato**. Y, **paralelamente**, Amélie se va dando cuenta de que **alguien le interesa románticamente**: un joven raro llamado **Nino** que colecciona fotos rotas de fotomatones de la ciudad y las pega en un álbum.
Esa subtrama (Amélie e Nino) **es el motor** de la segunda mitad de la película: Amélie intenta acercarse a Nino, pero **lo hace por procedimiento indirecto** (escondiéndose, dejándole pistas, jugando al gato y el ratón) en lugar de hablarle directamente. Su misma facilidad para hacer felices a otros se convierte en **una incapacidad para acercarse a su propia felicidad personal**.
Y la película es, en el fondo, **el camino de Amélie hacia entender que ella también merece intervenir en su propia vida**. No solo en la de los demás.
## La estética: el rojo, el verde, el dorado

**Jean-Pierre Jeunet**, el director, venía de hacer películas con una paleta **fuerte y artificial**: *Delicatessen*, *La ciudad de los niños perdidos*, *Alien resurrección*. Películas con un universo visual muy estilizado, casi de cómic. *Amélie* es **la culminación de ese estilo**, pero aplicado a un mundo cotidiano (París contemporáneo) en lugar de a una distopía.
La paleta visual está **deliberadamente saturada**. Hay tres colores que dominan:
- **Rojo intenso**: las cortinas, las paredes del café, los abrigos.
- **Verde saturado**: las paredes del apartamento de Amélie, los espacios públicos.
- **Amarillo dorado**: la luz, casi siempre una luz cálida de tarde, casi nunca neutra.
Esa paleta es **el equivalente visual** de la actitud de Amélie ante la vida: **ver el mundo no como es, sino como podría ser si miráramos con asombro**. Y la película te enseña, durante dos horas, **cómo se mira con asombro**. Cuando sales de la película, los primeros minutos en la calle **ves la realidad como si tuvieras un filtro Amélie encima**: te fijas en el pliegue de un mantel, en la forma de una vieja farola, en la cara concentrada de un señor leyendo el periódico. **La película te entrena la mirada** durante un par de horas. Y eso, antes del bombardeo de Instagram que vino diez años después, era una **revolución silenciosa de la sensibilidad**.
## Audrey Tautou: la cara que se quedó marcada
Audrey Tautou tenía 24 años cuando se rodó la película. Era una actriz casi desconocida (había hecho un par de papeles secundarios). Jeunet la eligió porque **tenía la mirada que él buscaba**: una mirada limpia, ligeramente sorprendida, con un punto de niña perpetua que no había crecido del todo. Y, sobre todo, **una sonrisa que se construye desde dentro**, lentamente, como si Amélie estuviera procesando lo que ve antes de decidir si reírse.
Tautou hace algo muy difícil con Amélie: **construye un personaje aparentemente plano que, mirado de cerca, es muy hondo**. Amélie podría parecer una soñadora superficial. Pero, viendo a Tautou en pantalla, **ves los micromovimientos**: la pausa antes de hablar, el gesto pequeño del cuello cuando piensa, el pliegue de la frente cuando algo no encaja. Es **una interpretación muy controlada**, no improvisada, no espontánea. Audrey Tautou **calculó cada gesto** con Jeunet en preproducción.
Y, además, Tautou es **fotogénica de una manera muy particular**: su cara funciona en los planos cerrados, su sonrisa tiene timing, su forma de mirar a cámara directamente (cosa que hace varias veces en la película, **rompiendo la cuarta pared**) es **encantadora sin ser falsa**. Esa facilidad para mirar a cámara y hacerlo bien es **una habilidad rara** y **es lo que convirtió a Amélie en icono**.
Después de *Amélie*, Tautou ha hecho una buena carrera (*El código Da Vinci*, *Una larga espera*, *El espantapájaros*, varias películas francesas), pero **ninguna ha vuelto a tener el peso cultural** de su primer protagónico. Es uno de esos casos en los que **una actriz se convierte para siempre en su personaje**, hasta el punto de que es difícil verla en otros papeles sin pensar **"ah, Amélie"**. No sé si para ella es una bendición o una maldición.
## La música de Yann Tiersen
Una nota sobre la banda sonora porque **es decisiva**. **Yann Tiersen** era un músico bretón relativamente desconocido cuando Jeunet lo eligió. Hacía música minimalista para pequeño formato: piano, acordeón, violín, instrumentos que llamaba **de cámara doméstica**. Jeunet había escuchado un disco suyo, *Tout est calme*, y decidió que **esa era la música** que quería para Amélie.
La banda sonora es **íntegramente de acordeón, piano y un toallín de cuerda**, sin orquesta sinfónica, sin grandes movimientos, sin grandilocuencia. Eso es **lo opuesto** a la mayoría de bandas sonoras de cine. Y es **exactamente lo que Amélie necesitaba**. La música de Tiersen tiene **el tamaño exacto** de la película: pequeño, íntimo, repetitivo, con motivos que vuelven y se transforman ligeramente.
Hay tres temas que **todos hemos oído en algún sitio** sin saber que eran de *Amélie*:
- ***Comptine d'un autre été***: la pieza de piano más famosa de la banda sonora, repetida en mil anuncios, mil videos de YouTube, mil clases de piano de aprendices. Es **una melodía simple, en menor, con un patrón cíclico** que hipnotiza.
- ***La valse d'Amélie***: el tema principal, en compás de tres, con acordeón liderando, **el sonido oficial** de París en pantalla durante una década entera.
- ***La noyée***: una pieza más oscura, con piano y violín, que aparece en los momentos más reflexivos de la película.
Tiersen, después de *Amélie*, se convirtió en uno de los compositores más imitados de cine y videojuegos. **Su sonido es el sonido de la felicidad cotidiana** del cine de los 2000. Pero la fuente original sigue siendo **superior** a las imitaciones.
## París como personaje
París en *Amélie* **no es París**. Es **una versión idealizada de París**: una París sin basura, sin pintadas en las paredes, sin turistas chinos haciéndose fotos delante de la torre Eiffel, sin obras, sin problemas de inmigración, sin conflictos sociales. Es **la París que los parisinos ya no tienen**, **la París que los visitantes querrían ver**, **la París de la postal**.
Eso ha sido **objeto de crítica**, sobre todo por parte de los franceses. Algunos cineastas (como Jean-Pierre Mocky) acusaron a *Amélie* de **lavarle la cara a una ciudad real, ocultando los problemas**. La crítica tiene una parte de razón: la París de *Amélie* es **una construcción**, no una representación documental.
Pero, en mi opinión, **eso no es un defecto**, es **una decisión estética**. La película no pretende ser un documental sobre París; pretende ser **una fábula moderna sobre la felicidad cotidiana**, y para eso necesita una París embellecida. Si Amélie viviera en una París realista (con basura, ruido, gritos, tráfico, polución), **el cuento no podría existir**. La estilización de la ciudad es **necesaria** para sostener el tono.
Lo que sí es legítimo decir es que la película ha contribuido a **un mito de París** que probablemente engaña a millones de turistas que llegan esperando encontrarse con la ciudad de la película. Esa decepción es **culpa parcial de Jeunet**. Y, en cierto modo, también es **culpa de Woody Allen** años después, con *Medianoche en París*. París, en cine, **se ha vuelto una idea**. Y eso le hace daño a la ciudad real, claro.
## La filosofía detrás: pequeñas felicidades como ética
Hay **una filosofía detrás** de *Amélie* que merece la pena explicitar. La película defiende, sin pretensión académica pero con claridad, **una ética de la atención**.
Esta ética es **más subversiva de lo que parece** porque va a contracorriente de **dos discursos dominantes**:
- **El discurso del éxito**: que la vida buena es la vida con grandes logros, grandes proyectos, grandes ambiciones. Amélie **no tiene nada de eso**, y la película no la presenta como insuficiente; la presenta como plena.
- **El discurso de la denuncia**: que la vida buena es la vida comprometida con causas, con luchas políticas o sociales, con grandes preocupaciones por el mundo. Amélie **tampoco tiene nada de eso**: lo que hace son intervenciones diminutas en vidas individuales. Y la película no la presenta como egoísta; la presenta como atenta.
Lo que defiende *Amélie*, sin moralina, es **una tercera vía**: que la vida buena puede consistir en **prestar atención a lo pequeño** y **intervenir en lo cercano** sin pretender cambiar el mundo. Eso, en 2001, era **una posición casi underground**. Hoy, con 25 años de distancia, es una posición que **mucha gente ha redescubierto** después del agotamiento de los grandes discursos.
Y, en la práctica, esa ética **funciona**. Si miras a tu alrededor, las personas que mejor viven (en mi experiencia) **no son ni los más exitosos ni los más comprometidos**: son **los más atentos**. Los que se dan cuenta cuando un amigo está triste. Los que se acuerdan del cumpleaños del cuñado. Los que llaman cuando ven una noticia que les hace pensar en alguien. Los que, sin grandes alharacas, **están presentes**. Esa atención, multiplicada por mil pequeños actos a lo largo de la vida, **es** una forma de bondad. Y *Amélie* es **el tratado de esa forma de bondad**.
## Lo personal: por qué me llega
Yo vi *Amélie* el año en que se estrenó (2001) en una sala de cine de Madrid. Iba con un amigo, sin esperar gran cosa. Salí completamente conquistado. Es **una de esas películas que te hacen mirar la calle de manera distinta** durante varios días después.
A los cuarenta y muchos, la película me sigue funcionando, pero por razones distintas. La primera vez me llegaba **el carrusel visual** de la película: la luz, la música, los colores. Veinticinco años después, me llega **la idea principal**: la atención al detalle como forma de cuidado. **Y eso entronca con cómo me he ido haciendo padre**.
Cuando uno tiene tres hijas, **se va dando cuenta** de que la paternidad **no se hace en grandes momentos**. Los grandes momentos (las fiestas, los cumpleaños, las vacaciones) son **una pequeña parte**. La paternidad de verdad se hace en **los pequeños momentos**: ir a buscarlas al colegio y notar que una está triste y preguntarle por qué; acordarse de comprar el yogur de fresa que solo le gusta a la mediana; ver una noticia y cortar el periódico para llevárselo a la mayor porque le interesa; escribirle un mensaje a la pequeña en mitad del día porque sí. **Esos son los gestos que se acumulan** y construyen **la relación de fondo**. Igual que Amélie con sus vecinos.
La película, sin pretender hacer pedagogía sobre la paternidad, **me ha enseñado mucho sobre paternidad**. Porque **la paternidad es exactamente lo que hace Amélie con su mundo**: prestar atención y intervenir en pequeñas dosis, con discreción.
Y hay otra cosa, más íntima, que diré rápido: **soy introvertido**. Eso lo saben quienes leen este blog. Una de las cosas que me hacen difíciles ciertas situaciones sociales es la **dificultad para acercarme directamente** a alguien que me interesa. Amélie sufre exactamente lo mismo: **es incapaz de acercarse a Nino frontalmente**, y por eso usa rodeos, juegos, pistas. Cuando vi la película por primera vez, me reconocí en eso de manera incómoda. La diferencia es que Amélie, al final, **da el paso**. Y la película es, entre otras cosas, **una invitación a quien se reconoce en ella a dar el paso también**. Sin acelerar, sin forzar, pero al final dándolo. Esa lección me llegó a los treinta y me ha durado.
## Las críticas a la película
Por honestidad: *Amélie* tiene críticos legítimos, y conviene mencionarlos.
**Crítica uno: la película es cursi.** Tiene su parte de razón: hay momentos en que la película se acerca a la **dulzura** de manera arriesgada. La voz en off es muy enfática. La música a veces refuerza demasiado lo que ya estamos viendo. Algunos planos están **más cargados de lo necesario**. Si lo cursi te resulta insoportable, no la disfrutarás.
**Crítica dos: la París representada es ahistórica.** Algunos críticos han señalado que la París de *Amélie* es **una París sin árabes, sin negros, sin musulmanes, sin obreros, sin tensiones sociales**. Esa crítica es certera y conviene reconocerla. La película, **deliberadamente o no**, presenta **una París blanca, de clases medias bohemias**, que no se corresponde con la París real de 2001 (mucho menos de 2026). Como crítica social, la película **falla**. Como cuento moderno, **no pretendía ser otra cosa**.
**Crítica tres: el personaje de Amélie es paternalista.** La idea de "intervenir en las vidas de otros para hacerlos felices sin pedirles permiso" puede leerse como **una forma encubierta de paternalismo**. ¿Quién es Amélie para decidir cuándo y cómo intervenir en la vida de su vecino? Esta crítica es interesante. La película no la responde explícitamente, pero **podría**: lo que hace Amélie es **discreto, no invasivo**, y siempre busca **devolverle al otro algo que ya era suyo** (la caja de tesoros, una carta perdida, una pista para encontrar a alguien). No le impone nada nuevo. Le devuelve lo que ya tenía. Eso es **menos paternalista de lo que parece**.
## A quién se la recomiendo
Se la recomiendo a:
- **Cualquiera que necesite un respiro**. Es la película que pones cuando llevas semanas mirando malas noticias.
- **Adolescentes y veinteañeros**. Tiene la velocidad y el tono que les llega.
- **Personas que disfrutan los detalles**. Los que se fijan en las texturas, en los colores, en los sonidos.
- **Quien tenga una "Amélie" interior** (es decir, casi todo el mundo, pero especialmente los introvertidos).
No se la recomiendo a:
- **Quien sea alérgico al sentimentalismo francés**. No se va a dejar.
- **Quien busque cine de denuncia**. Aquí no hay.
- **Quien crea que la felicidad solo se construye en grandes proyectos**. Esta película va en otra dirección.
## Cómo verla
**Versión original en francés con subtítulos**, sin discusión. La doblada al español está bien pero la voz en off original (Andre Dussollier) es **uno de los grandes activos de la película**. Doblada se pierde **gran parte del tono**.
**Tarde de fin de semana, café o té**, mantita opcional. Es una película de luz cálida, casi de invierno con calefacción. Pega especialmente bien con tiempo gris.
**Si la has visto, vuélvela a ver cada cinco años**. Cambia con la edad de quien la ve. La que ves a los veinte (alegría visual) no es la que ves a los cincuenta (filosofía operativa).
## El tono entre películas: un ejercicio
Como esta es la novena de la lista, voy a hacer **un pequeño ejercicio de comparación**. Si alineas las nueve películas que ya he soltado (la diez la dejo para mañana), te das cuenta de **algo curioso**: están organizadas, sin que yo lo haya planificado, en un crescendo y luego un descenso de **densidad emocional**.
- 1 (Cinema Paradiso): nostalgia + emoción intensa
- 2 (Cadena perpetua): esperanza + emoción intensa
- 3 (El padrino): tragedia + emoción intensa
- 4 (Casablanca): sacrificio + emoción intensa
- 5 (La vida es bella): horror + emoción intensa
- 6 (Forrest Gump): vida + emoción media
- 7 (La lista de Schindler): horror + emoción intensa (otra vez)
- 8 (Érase una vez en América): memoria + emoción intensa
- **9 (Amélie): alegría + emoción ligera**
*Amélie* es **la nota distinta**. Es la pieza ligera dentro de una sinfonía pesada. Y precisamente por eso le he reservado el penúltimo lugar: para que, antes de cerrar la serie con la décima (que es otro registro otra vez), **aparezca el contrapunto**.
Esa secuencia no estaba planificada. Pero, mirándola completa, **tiene su lógica**: ningún top diez personal puede ser monocromo. La vida no lo es. Y mis películas favoritas tampoco.
## Lo que viene mañana
**Cierre de serie**. La décima. Una película de **1957**, en blanco y negro, **rodada en un solo escenario**, con **doce hombres** y **una sola escena**: la deliberación de un jurado. Si conoces el cine, ya sabes cuál es. Si no, sorpresa mañana. Será el cierre que **deja la serie**.
Diez películas. Un mes y medio de serie. Hasta mañana.
---
# Fuck you money VI: tiempo > dinero
URL: https://javiervalencia.net/post/fuck-you-money-tiempo-vs-dinero
Llegamos al pilar que más te hace replantearte cosas. Los cinco anteriores han ido de ahorro, inversión y gestión del consumo. Este va de algo distinto: **cómo valoras tu tiempo**. Porque la filosofía [fuck you money de Joan Tubau](/post/fuck-you-money-joan-tubau) no consiste en acumular el máximo dinero posible. Consiste en acumular **la máxima libertad por hora trabajada**. Y esas dos cosas no son la misma.
## El cálculo que casi nadie hace
Pongamos dos ofertas de trabajo.
**Oferta A**: 45.000 € brutos al año. Jornada de 40 horas semanales. 30 días de vacaciones. Sin guardias. Sin horas extra. Trabajo desde casa la mitad de la semana. Reuniones razonables.
**Oferta B**: 75.000 € brutos al año. Jornada nominal de 40 horas. En la práctica, 55-60. Tres guardias al mes. Llamadas de WhatsApp del jefe los domingos. Presencialidad obligatoria.
A primera vista, B paga un 67% más. Pero hagamos el cálculo de **salario efectivo por hora**:
| | Oferta A | Oferta B |
|---|---|---|
| Bruto anual | 45.000 € | 75.000 € |
| Neto aproximado (España) | ~33.000 € | ~50.000 € |
| Horas trabajadas/semana | 40 | 57 |
| Semanas/año (descontando vacaciones) | 46 | 46 |
| Horas/año | 1.840 | 2.622 |
| **€ netos por hora trabajada** | **17,9 €/h** | **19,1 €/h** |
La oferta B paga **un 7% más por hora**. Y eso ignora el coste del estrés crónico, las cenas familiares interrumpidas, los fines de semana de guardia. Una vez ajustas por desgaste real, **B paga peor por hora que A**.
Este cálculo casi nadie lo hace. Y es la pieza central del pilar.
## El coste opaco de un sueldo alto
Tubau insiste mucho en que el sueldo bruto **es un número engañoso**. Hay que mirar siempre lo que **te queda en mano**, por hora, **descontando el coste real del puesto**.
Costes opacos de un sueldo alto que rara vez se cuentan:
- **Tiempo extra real** (no el del contrato).
- **Comer fuera** porque no tienes tiempo de cocinar (50-80 €/semana extra).
- **Servicios delegados**: limpieza del piso, plancha, comida a domicilio (200-400 €/mes).
- **Coste mental**: dormir peor, pelearte con tu pareja, no ver a tus hijos.
- **Salud**: ansiedad, falta de ejercicio, peor dieta. A veces el coste no aparece hasta los 50 años, pero aparece.
- **Coste de mantenimiento del estilo de vida** asociado al puesto: ropa de marca, coche que «toca», restaurantes con clientes.
- **Coste fiscal marginal**: en España, cada euro extra entre 50.000 y 60.000 brutos tributa al ~37-45% según la comunidad. Mucho del aumento se queda Hacienda.
Cuando sumas todo, **el salario neto efectivo por hora libre suele ser mejor en el trabajo «menor»**.
## La curva de utilidad del dinero
Aquí entra una idea de Daniel Kahneman que Tubau cita: hasta cierto nivel de ingreso, **el dinero compra felicidad**. Por encima de ese nivel, **la compra es marginal**.
Los estudios más recientes (Killingsworth, 2023) lo cuantifican en torno a **75.000-100.000 dólares anuales en EE. UU.**, que en España equivaldría aproximadamente a **45.000-60.000 € netos al año por hogar**. Por encima de ese umbral, más dinero apenas mueve la aguja de la satisfacción vital, mientras que las horas que «cuesta» ese dinero extra sí restan calidad de vida de forma medible.
Esto no es decir que ganar más sea malo. Es decir que **ganar mucho más solo merece la pena si las horas extra no son tan malas, o si no son extra**. Un emprendedor que disfruta lo que hace puede meter 70 horas semanales sin descontento. Un empleado infeliz que mete 55 sufre cada hora.
La pregunta correcta no es «¿cuánto ganaré?», es:
> «¿Cuánta de mi vida estoy dispuesto a vender, y a qué precio por hora?»
## Trabajos que parecen buenos y no lo son
Algunos perfiles que Tubau suele criticar como engañosos:
**Consultoría estratégica de élite (McKinsey, BCG, Bain)**: sueldos brutales, 70 horas semanales, viajes constantes, alta presión. Funciona durante 2-3 años para gente joven sin ataduras. Como modelo de vida sostenido, destruye relaciones, salud y tiempo libre. El coste por hora es a menudo peor que un ingeniero en una empresa de producto.
**Banca de inversión**: similar pero más extremo. Sueldos altísimos en el primer año, bonus que ata a la silla, horarios infames. Tasa de divorcio y problemas de salud mental por encima de la media.
**Startups «con stock options»**: la mayoría de las stock options valen cero. El sueldo bruto suele ser inferior al de mercado y las horas, peores. Es defendible si crees genuinamente en el proyecto y quieres aprender; es un mal negocio si te lo vendieron como «el siguiente unicornio».
**Funcionariado con guardias y horas brutales** (médicos hospitalarios, fuerzas de seguridad): sueldos decentes pero horas reales muy por encima de las contractuales. Hay que mirar el efectivo.
**Comerciales con variable alto**: parecen ganar mucho los buenos años. Los malos años el variable desaparece y la base es ridícula. Ingreso medio: la mitad del nominal.
Trabajos que parecen menos brillantes y suelen serlo más:
**Funcionariado «de oficina»** con horarios razonables, estabilidad, jornada continua: sueldo medio, pero coste por hora excelente y libertad total fuera del trabajo.
**Empresa de producto SaaS estable** con cultura de no-overtime: sueldo decente, horas razonables, posibilidad real de hacer carrera sin destruirse.
**Autónomo con cartera estable** y tarifas decentes: techo de cristal, pero **control total del calendario**. Para muchos, mejor que un sueldo más alto.
Las jerarquías de prestigio no coinciden con las jerarquías de calidad de vida.
## La pregunta del tiempo libre
Hay un test que Tubau plantea de forma recurrente:
> «¿Si te tocara la lotería mañana, seguirías en este trabajo?»
Las respuestas posibles:
- **Sí, igual**: enhorabuena, tienes el trabajo correcto.
- **Sí, pero cambiando estas tres cosas**: tienes el trabajo correcto al 80%, considera negociar.
- **No, lo dejaría hoy**: estás vendiendo años de vida por un sueldo. Empieza a planificar la salida.
- **No tengo ni idea**: pregunta más importante que ninguna respuesta financiera.
El *fuck you money* es la herramienta que te permite **convertir la respuesta «no» en una decisión real**, no en una queja de café.
## El otro lado: no idealizar el ocio
Una nota importante para no caer en el extremo opuesto. Tubau no defiende vivir sin trabajar. Defiende **elegir cómo trabajas**.
El trabajo bien elegido es una fuente de identidad, sentido y aprendizaje. La gente que se jubila de golpe a los 55 con FU money y sin haber pensado qué iba a hacer con el resto de su vida, a menudo entra en depresión a los 18 meses. El problema no es el trabajo; es **trabajar lo que no quieres con quien no quieres por dinero que no necesitas**.
La meta no es trabajar cero horas. Es trabajar **las horas que tú elijas, en lo que te aporte, con la gente que respetes**. Eso es lo que el dinero compra. No la inactividad.
## Mi versión
Yo lo aplico de forma muy concreta. Cuando me ofrecen un cambio de puesto, hago tres números:
1. **Salario efectivo por hora** del nuevo puesto vs el actual.
2. **Días libres reales** (no contractuales) que voy a tener al año.
3. **Distancia al ordenador del jefe**: si tengo que estar visible cada hora, vale menos que si puedo cerrar el portátil a las seis sin que nadie llame.
Si el nuevo puesto pierde en cualquiera de los tres, no compensa, aunque pague un 30% más en bruto.
Y al revés: si una oferta paga lo mismo o menos pero gano 5 horas a la semana y desaparecen las guardias, **es subida de sueldo encubierta**. La que se traduce en libertad real.
En el próximo post entramos en el séptimo pilar: **optionality**, donde dejamos de hablar de tiempo y dinero por separado y empezamos a hablar de la libertad de poder decir «que te jodan» como una opción concreta que el dinero compra.
---
# Rails sostenible (I): qué es la sostenibilidad y la arquitectura de Rails
URL: https://javiervalencia.net/post/rails-sostenible-i-sostenibilidad-y-arquitectura
*Primera entrega de la serie **[Rails sostenible](/search?tag=rails-sostenible)**, un recorrido en diez posts por el libro **Sustainable Web Development with Ruby on Rails**, de David Bryant Copeland. Tiempo de lectura estimado: 12 minutos.*
Llevaba tiempo queriendo sentarme con un libro que no fuera otro tutorial de "monta un blog en Rails en 20 minutos". Copeland propone otra cosa: no enseñarte a *empezar* un proyecto en Rails, sino a mantenerlo vivo durante años sin que el coste de cambiarlo se dispare. Esa palabra —**sostenibilidad**— es el hilo que cose todo el libro, y voy a dedicarle diez entradas porque creo que es de los pocos libros de Rails que envejecen bien.
En esta serie mezclo tres cosas: un resumen fiel de lo que dice el libro, mi propia experiencia aplicando (o sufriendo la ausencia de) estas ideas en proyectos reales, y alguna digresión cuando el tema lo pide. Empecemos por los cimientos: qué entiende Copeland por sostenibilidad y por qué la arquitectura por defecto de Rails es, a la vez, su mayor virtud y su trampa más sutil.
## Qué es la sostenibilidad del software
La definición de Copeland es deliberadamente sobria. Un sistema es **sostenible** cuando el equipo puede seguir respondiendo a las necesidades del negocio —arreglar bugs, añadir features, adaptarse a cambios— a un ritmo más o menos constante a lo largo del tiempo. No es velocidad inicial. No es elegancia. Es que el cambio número 500 no cueste diez veces más que el cambio número 5.
Esto suena obvio hasta que recuerdas cuántos proyectos has visto donde tocar cualquier cosa daba miedo. Donde añadir un campo a un formulario implicaba rezar. La insostenibilidad no llega de golpe: se acumula como sedimento, una decisión cómoda detrás de otra, hasta que un día el equipo se pasa el 80% del tiempo peleándose con su propio código y el 20% entregando valor.
Copeland insiste en tres cualidades que hacen sostenible a un sistema:
- **Que sea fácil de entender.** Alguien que llega nuevo debería poder leer el código y formarse un modelo mental correcto sin arqueología.
- **Que sea fácil de cambiar.** Modificar un comportamiento no debería obligar a tocar quince sitios ni a entender todo el sistema.
- **Que sea fácil de poner a funcionar.** Un desarrollador nuevo debería tener el proyecto corriendo en su máquina en minutos, no en días.
Las tres son medibles en la práctica, y las tres se erosionan calladamente si nadie las defiende.
## Carrying cost y opportunity cost
Aquí está, para mí, la idea más valiosa del primer capítulo, y la que más uso para argumentar en reuniones. Copeland toma prestado del mundo financiero dos conceptos:
- **Opportunity cost** (coste de oportunidad): lo que dejas de hacer por dedicarte a otra cosa. Si paso tres semanas montando un sistema de plugins que nadie ha pedido, el coste de oportunidad son las tres features que no entregué.
- **Carrying cost** (coste de mantenimiento o "de acarreo"): el coste *continuo* de mantener viva una decisión. Cada dependencia que añades, cada abstracción que introduces, cada microservicio que despliegas tiene un carrying cost: hay que actualizarlo, entenderlo, monitorizarlo y arreglarlo cuando se rompe.
> El código no es un activo, es un pasivo. Cada línea que escribes es una línea que alguien tendrá que entender, mantener y, algún día, borrar.
La conclusión práctica es brutal y libera mucho: **la decisión sostenible casi nunca es la más sofisticada**. Es la que minimiza el carrying cost futuro. Un poco de duplicación honesta puede ser más barata de mantener que una abstracción prematura que nadie entiende seis meses después. Copeland no es dogmático con esto —no predica el "no abstraigas nunca"—, pero te obliga a preguntarte siempre: *¿cuánto me va a costar mantener esto vivo?*

## Las asunciones del libro
Copeland es honesto sobre el contexto en el que sus consejos tienen sentido. El libro asume que:
1. **El software tiene un propósito claro.** No es un experimento desechable ni un prototipo de fin de semana.
2. **El software necesita existir durante años.** Si tu proyecto va a vivir tres meses, optimizar para la sostenibilidad es tirar el dinero.
3. **El software va a evolucionar.** Los requisitos cambiarán, porque siempre cambian.
4. **El equipo va a cambiar.** Quien escribió el código no será necesariamente quien lo mantenga. El código tiene que sobrevivir a las personas.
Me parece importante subrayar esto porque mucha gente rechaza estos libros con un "para mi caso es overkill". Y a veces tienen razón: un script interno que usan tres personas no necesita una capa de servicios. Pero la mayoría del software profesional cumple esas cuatro asunciones, aunque al principio finjamos que no.
## La arquitectura de Rails: su virtud y su trampa
Rails ganó por una razón: las **convenciones**. No discutes dónde va cada cosa, no configuras cuarenta archivos XML, no eliges entre quince librerías para cada tarea. Rails tiene opiniones fuertes y eso te hace productivo desde el minuto uno. Copeland lo reconoce sin complejos: para un enorme abanico de aplicaciones, Rails sigue siendo la opción más sostenible que existe.
Pero entonces hace una disección incómoda del MVC tal y como Rails lo entiende. Rails te ofrece, esencialmente, tres sitios donde poner cosas:
- **Vistas:** la capa de presentación (plantillas, helpers).
- **Modelos:** que en Rails significa, casi siempre, *Active Records* —objetos atados a una tabla de la base de datos.
- **Todo lo demás:** controladores, jobs, mailers... las clases "frontera" que conectan Rails con el mundo.
¿Y dónde va la **lógica de negocio**? Esa es la pregunta del millón, y la respuesta por defecto de Rails es: *en ninguna parte concreta*. No hay una carpeta `app/business_logic`. No hay una convención. Así que la lógica de negocio, que es lo que de verdad hace especial a tu aplicación, acaba derramándose en los dos únicos sitios disponibles: los modelos y los controladores.

David Heinemeier Hansson, creador de Ruby on Rails. Foto: CC BY 2.0, Wikimedia Commons.
### Por qué eso es un problema
Cuando metes la lógica de negocio dentro de los Active Records pasa algo predecible y dañino. Los modelos son **clases muy usadas**: el `User`, el `Order`, el `Product` los referencia medio sistema. Y son clases con **mucho churn** (rotación): cambian constantemente porque el negocio cambia constantemente.
Junta las dos cosas —clases críticas, muy referenciadas, que además cambian a todas horas— y tienes la receta perfecta de la insostenibilidad: un bug en `User` salpica a media aplicación, y cada nueva regla de negocio engorda una clase que ya pesaba 800 líneas. El famoso *fat model* no es una virtud, es deuda disfrazada de buena práctica.
Copeland no propone tirar Rails por la ventana ni montar una arquitectura hexagonal con cuatro capas de indirección (eso tendría su propio carrying cost). Propone un ajuste quirúrgico, que será el corazón de toda la serie: **darle a la lógica de negocio su propia casa**, fuera de los Active Records, en una capa de servicios explícita. Lo veremos en detalle en la tercera entrega.
## Mi versión
He vivido las dos caras de esto. En proyectos donde la lógica de negocio vivía dentro de los modelos, el síntoma siempre era el mismo: nadie se atrevía a tocar el modelo `User` sin convocar una reunión. La clase tenía callbacks que disparaban emails, validaciones que dependían del contexto, métodos de cálculo de precios y hasta lógica de permisos. Tocar una cosa rompía otra a tres saltos de distancia.
Lo de `carrying cost` me lo he tatuado mentalmente. Cada vez que alguien propone "y montamos esto con una cola de mensajes y dos microservicios", la pregunta no es *si se puede* —casi todo se puede—, sino *quién lo va a mantener despierto a las tres de la mañana dentro de dos años*. La mayoría de las veces, la respuesta sensata es un monolito Rails aburrido y bien organizado. Copeland me dio el vocabulario para defender esa postura sin sonar a vago.
Y sobre las asunciones: he aprendido a preguntarlas explícitamente al empezar. "¿Esto va a vivir años o es un experimento?" cambia por completo cuánto esfuerzo merece la pena invertir en estructura. No hay una respuesta universal; hay una respuesta correcta *para tu contexto*.
## Lo que viene
Hemos puesto los cimientos conceptuales: sostenibilidad como capacidad de cambiar a coste constante, el carrying cost como brújula para decidir, y el diagnóstico de que la arquitectura de Rails, siendo magnífica, deja a la lógica de negocio sin hogar.
En el **próximo post** bajamos al barro práctico: cómo arrancar una app Rails "con buen pie", con un entorno de desarrollo reproducible —`bin/setup`, `bin/run`, `bin/ci`—, configuración por variables de entorno y logging de producción decente desde el día uno. Porque la sostenibilidad empieza, literalmente, por que alguien nuevo pueda clonar el repo y tenerlo funcionando en cinco minutos.
---
# Las diez mejores películas de mi vida: 8. Érase una vez en América
URL: https://javiervalencia.net/post/top-10-peliculas-8-erase-una-vez-en-america
*Érase una vez en América* es **la película más larga de mi top diez**. En su versión original (la europea, de 1984) dura **tres horas y cuarenta y nueve minutos**. En la versión que se restauró años después, todavía un poco más. Y cualquiera de las dos versiones **se queda corta**, porque cuando termina la película te quedas con la sensación de haber acompañado a sus protagonistas durante setenta años de vida y de querer **un epílogo más**.
Esta es la octava entrega de la serie. Si entras frío, conviene leer primero la [número uno](/post/top-10-peliculas-1-cinema-paradiso). La serie va de la uno hacia abajo. Las siete anteriores están publicadas. Hoy, la ocho.
## La obra final de Leone

**Sergio Leone**, italiano de Roma, hijo de un director de cine de los años treinta, había revolucionado el género del western en los sesenta con la trilogía del dólar (*Por un puñado de dólares*, *La muerte tenía un precio*, *El bueno, el feo y el malo*) protagonizada por Clint Eastwood. Después había hecho **una de las mejores películas de la historia del cine**, *Hasta que llegó su hora* (*Once Upon a Time in the West*, 1968), un western elegíaco con Henry Fonda, Charles Bronson y Claudia Cardinale, que muchos consideran su obra maestra. Después de esa, Leone se retiró parcialmente, produjo otras películas, esperó.
Y, durante **doce años**, fue preparando lo que sería su última obra: *Érase una vez en América*. Trabajó en el guion durante años. Buscó financiación durante años. Negoció derechos durante años. Cuando por fin logró rodarla, llegó **al borde del agotamiento**: la rodó con cincuenta y tantos años, gastó toda su energía en ella, y murió cinco años después, en 1989, sin haber hecho otra película. *Érase una vez en América* es, por tanto, **el testamento cinematográfico** del director más rítmico y operístico del cine italiano.
Y, como obra final, **es una obra mayor**. Tres horas y media en las que Leone hace **lo que mejor sabía hacer**: filmar a hombres mirándose a los ojos en silencio durante minutos, con música de Morricone debajo, mientras el espectador siente que está viendo **el peso del tiempo** sobre las caras.
## La estructura imposible
La película cuenta **cuarenta años de la vida de unos amigos**, judíos del Lower East Side de Nueva York. Pero **no la cuenta linealmente**. La cuenta **alternando tres tiempos**:
- **Años veinte**: la infancia de los protagonistas como bandilla de chiquillos en las calles de Nueva York en plena Prohibición. Hacen recados para mafiosos locales, roban, pelean con otras bandillas, descubren el dinero, se hacen amigos para siempre.
- **Años treinta**: la juventud y madurez de los mismos personajes ya como **gánsters profesionales**, con la Prohibición todavía en marcha. Negocios, traiciones, mujeres, relaciones con la mafia, el final de la Prohibición y lo que pasa después.
- **Años sesenta**: el regreso del protagonista, **Noodles** (David Aaronson), a Nueva York después de treinta años escondido. La razón del regreso, los reencuentros con quienes quedan vivos, la búsqueda de respuestas sobre lo que pasó en los años treinta.
Las tres líneas temporales **no se cuentan en orden** sino **alternándose constantemente**, sin avisos ni cortes nítidos. Una escena en los sesenta se enlaza con una en los veinte, esa con una en los treinta, vuelve a los sesenta. **El espectador tarda un buen rato en entender el sistema**. Y, cuando lo entiende, **se le ofrece una de las experiencias narrativas más complejas y satisfactorias del cine**.
Esa estructura **no es un capricho**. Funciona como **mapa mental** del propio Noodles, que en los sesenta vuelve a Nueva York con la cabeza llena de recuerdos, de remordimientos, de imágenes inconexas. La película **es Noodles recordando**, no es un narrador omnisciente. Por eso la cronología baila: porque **la memoria baila**.
## La pregunta que la película plantea
La pregunta central es **una pregunta sobre la culpa y el remordimiento**. En los años treinta, Noodles cree haber denunciado a sus tres mejores amigos a la policía como manera de impedir un golpe que él consideraba suicida. Cree que sus amigos murieron en el tiroteo subsiguiente con la policía, y vive con esa culpa **escondido durante treinta años**. En los sesenta, una carta misteriosa lo trae de vuelta a Nueva York, y descubre que **la realidad es otra**: uno de los amigos no murió, **se hizo pasar por muerto** y construyó una nueva identidad como político de Nueva York. Y, además, fue **ese amigo** el que, mucho antes, había orquestado todo: la decisión de hacer el golpe, la denuncia esperada de Noodles, la escena del tiroteo falso. Noodles ha vivido treinta años cargando con una culpa **que no era suya**.
¿Cuál es la lección? **No la hay clara**. Eso es lo que hace que la película sea grande: no te da una resolución moral simple. Noodles, al saberlo, **no se libera**. **No se venga**. **No abraza al amigo redivivo**. Se va. La última imagen del Noodles de los sesenta es una mirada larga, cargada, sin diálogo. Y, después, un flashback al Noodles de los años treinta, en un fumadero de opio, **sonriendo** con una sonrisa enigmática que ha sido objeto de mil interpretaciones.
¿Está sonriendo porque, en el fondo, todo lo que vino después fue un sueño de opio y nunca pasó? ¿Está sonriendo porque finalmente acepta el peso de su vida? ¿Está sonriendo porque ha entendido que la culpa que cargaba **no era real** y que puede dejar ir? La película **no responde**. Y por eso me gusta.
## Robert De Niro y la cara cansada

**Robert De Niro**, en una de sus tres o cuatro grandes interpretaciones, encarna a Noodles en los años treinta y los sesenta. (En los años veinte, Noodles es un niño y lo interpreta otro actor.) De Niro hace lo que es su gran fortaleza desde *Toro salvaje*: **construir un personaje a base de detalles físicos**. La forma de andar, la forma de fumar, la forma de mirar a una mujer, la forma de beber. Cada gesto es **deliberado**.
Lo más impresionante es **la transformación entre el Noodles de los treinta y el de los sesenta**. La película usa maquillaje para envejecer a De Niro, sí, pero **lo determinante no es el maquillaje**. Es **la forma en que De Niro mueve el cuerpo, el rostro, los hombros, la respiración**. El Noodles joven es un hombre **rabioso, lleno de energía contenida**. El Noodles viejo es un hombre **vaciado, cansado, lento, con la espalda redondeada**. La diferencia es física, no cosmética. Y eso es lo que hace que el envejecimiento sea creíble.
A su lado, **James Woods** como Max, el amigo (y traidor) de Noodles. Woods es **un actor que sabe interpretar la ambigüedad**: el espectador nunca sabe del todo si Max es bueno o malo, qué quiere, qué piensa. La química entre De Niro y Woods, especialmente en las escenas de los años treinta, es **una de las mejores de su carrera para los dos actores**.
Y los demás: **Elizabeth McGovern** como Deborah, la mujer que Noodles ama desde la infancia y que nunca consigue del todo; **Tuesday Weld** como Carol; **Jennifer Connelly** como la Deborah niña (en una de sus primeras interpretaciones); **Burt Young** como Joe; **Joe Pesci** en un secundario; **Treat Williams** como James O'Donnell. **Todos los papeles, hasta los más pequeños, son notables**. Leone, como Coppola en *El padrino*, era **obsesivo con el casting**. Cada cara cuenta. No hay un papel mal puesto.
## La música de Morricone (otra vez)
Hago lista mental de las películas de mi top diez en las que Morricone firma la banda sonora:
- Cinema Paradiso (la 1)
- Érase una vez en América (la 8)
Dos en mi top diez. Que **un solo compositor** tenga dos películas en mi top diez **dice más de Morricone que cualquier elogio**. Es **el mejor compositor de cine del siglo XX**. Punto. Sin discusión.
La banda sonora de *Érase una vez en América* es **una de las grandes de Morricone**, comparable solo a *Cinema Paradiso* y a *La misión*. El tema principal, **el "Deborah's Theme"**, es una **melodía con vocalización femenina** ejecutada por **Edda Dell'Orso**, la soprano italiana habitual en las películas de Morricone. La melodía se repite a lo largo de la película en distintos momentos, conectando los tres tiempos narrativos: cuando suena en los años veinte, te lleva a los treinta; cuando suena en los treinta, te trae los sesenta; cuando suena en los sesenta, te devuelve a los veinte. **Es el hilo musical** que cose las tres líneas temporales que el montaje desordena.
Hay otra pieza memorable: **la flauta solista** que toca Pinucio Costa (creo recordar) y que aparece en los momentos infantiles, especialmente cuando los chicos están en la calle del Lower East Side jugando, peleando, descubriendo. La flauta es **un instrumento simple**, casi infantil, y está perfectamente elegida para la **mirada nostálgica** de la película hacia la infancia.
Morricone compuso la banda sonora **antes del rodaje**, no después. Le dio las grabaciones a Leone, y Leone las puso en el set para que los actores rodaran las escenas **con la música ya sonando**. Eso, técnicamente, es **rarísimo**. Pero explica por qué la película tiene esa coherencia entre música e imagen: **están hechas a la vez**, no una después de la otra.
## La escena del puente, la escena de la cena, la escena del tren
He pensado mucho cuáles son las **tres escenas** que más se me han quedado de *Érase una vez en América*, y voy a soltarlas.
**La escena del puente.** Los dos amigos, Noodles y Max, ya hombres adultos, se encuentran en un puente de Nueva York por la noche. Hablan. Se miran. La cámara los mantiene a media distancia. La música baja. El diálogo es breve. **Y, sin embargo, en esa escena se decide el destino de los dos**. La capacidad de Leone para cargar una escena de bajo voltaje aparente con un significado enorme **es una de sus marcas autorales**. La escena dura unos cinco minutos y, viéndola, sientes que dura veinte. No por aburrimiento, sino porque **el tiempo se densifica**. Es lo que solía pasar también en sus westerns: dos pistoleros mirándose antes del duelo, cinco minutos sin diálogo, **toda la tensión del mundo**.
**La escena de la cena (la cena adulta de Deborah).** Noodles vuelve a Nueva York en los sesenta y se reencuentra con Deborah, su amor de la infancia, ya hecha actriz famosa, que ahora vive con otro hombre que es, sin que ella lo sepa al principio, el supuestamente muerto Max convertido en político. La escena de la cena, con los tres en una mesa elegante, donde Noodles se da cuenta de la verdad **muy poco a poco**, es **una de las cumbres dramáticas del cine**. **Sin gritos, sin escenas violentas**: solo tres personajes en una mesa, comiendo, hablando, mirándose. Y el espectador, **detrás de los ojos de Noodles**, vive el descubrimiento al mismo tiempo que él.
**La escena del tren.** Hacia el final, Noodles toma un tren y mira por la ventanilla. La cámara se queda en su cara durante un tiempo larguísimo. La luz cambia. La música entra. **No se dice una palabra**. Pero **en esa escena se cierra la película**. Es uno de los planos más largos de la historia del cine, y uno de los más cargados de significado. La cara de De Niro, **tras tres horas y media de película**, contiene **toda la película**. Es **una clase magistral de acción contenida**.
Hay más, claro. La escena del tiroteo en el almacén. La escena de los chicos descubriendo el contrabando. La escena del cumpleaños de Deborah cuando son niños. La escena del tren a Detroit. La escena del fumadero de opio. La escena del cementerio en los sesenta. **Cada una merece su propio análisis**. Pero las tres anteriores son las que vuelvo a buscar en mi cabeza con más frecuencia.
## La versión americana, la mutilación
Una nota crucial sobre la película: **existe una versión americana** que **no debería ver nadie nunca jamás**.
Cuando Leone entregó la película al estudio americano, el estudio decidió que **tres horas y media eran demasiadas** para el público de Estados Unidos. La cortaron sin preguntar a Leone. **La cortaron a dos horas y media**. Y, peor aún, **la pusieron en orden cronológico**, eliminando la estructura de tres tiempos que era el corazón formal de la película. La versión americana **destruyó la película**.
Leone se enteró y montó en cólera. Se negó a que su nombre apareciera en los créditos de la versión americana en algunos países. La taquilla americana fue **un fracaso** (con razón: la versión que se vio era inviable). En Europa, donde se distribuyó la versión completa, la película tuvo el éxito que merecía. Cannes la presentó. La crítica europea la consagró. **Hoy se considera una obra maestra**, pero esa consagración tardó porque **media generación** de americanos solo había visto la mutilación.
Si la vas a ver: **comprueba que es la versión completa**, de unas 3h49m. Si pone que dura 2h30m, no la veas. Es **otra película**.
Hay también, desde 2012, una **versión extendida** de unas 4h11m con escenas que Leone había rodado y que el montaje original había eliminado. Esa versión **es interesante para fans hardcore** pero, en mi opinión, **la versión europea original de 3h49m es la canónica**. Las escenas añadidas, aunque están bien, **rompen un poco el ritmo**.
## La decisión de filmar en Brooklyn (y en Roma, y en Venecia)
Una curiosidad técnica: **la película se rodó en cuatro países**. Las escenas de Nueva York se rodaron parcialmente en Nueva York real (las exteriores del Lower East Side) pero también en **Roma** (algunos interiores, donde Leone tenía su productora) y en **Venecia** (algunos exteriores que Leone consideró que se parecían más al Nueva York de los años veinte que la Nueva York actual). Y algunas secuencias se rodaron en **Quebec, Canadá**, por razones logísticas.
Esa **mezcla de locaciones reales y reconstruidas** es una de las cosas que hacen que la Nueva York de la película sea **a la vez fiel y de fantasía**. Es la Nueva York que Leone tenía **en la cabeza**, no necesariamente la Nueva York de los archivos. Y eso es **la marca del director auteur**: no documenta una época; **la imagina**.
## Lo personal: por qué me llega esta película
Lo confieso: la primera vez que vi *Érase una vez en América*, **no la entendí**. Era joven, no aguanté las tres horas y media de tirón, me perdí en los saltos temporales, no terminé de seguir quién era quién. La aparqué con la sensación de **película demasiado larga, demasiado lenta, demasiado pretensiosa**.
Volví a verla años después, **con tiempo y con paciencia**. Y entendí por qué los críticos la coloca en sus mejores listas. La he visto cuatro veces ya, **y cada vez encuentro cosas nuevas**. La primera vez te enteras de la trama. La segunda te empiezan a llegar los personajes. La tercera notas la estructura. La cuarta entiendes el peso de cada plano largo.
Lo que me llega de esta película, sobre todo, es **la idea de que la memoria es el verdadero protagonista**. Noodles, durante toda la película, **está recordando**. Y la película es **el contenido de su memoria**, no una crónica externa de lo que pasó. La memoria, como en la realidad, **es selectiva**: hay cosas que recuerda con detalles, otras que olvida, otras que ha **falsificado** sin saberlo.
Ese tema, **la memoria como obra**, me obsesiona desde hace años. Lo tengo presente cuando miro fotos viejas, cuando hablo con amigos de la infancia y descubrimos que recordamos lo mismo de manera distinta, cuando vuelvo a un sitio que conocí hace décadas y ya no es como yo lo recuerdo. **La memoria no es una grabación**: es **un relato**. Y el relato cambia con el tiempo. Eso es lo que cuenta esta película. Y por eso, a los cuarenta y tantos, me llega más que a los treinta.
Hay otro tema que me llega: **la idea de que las grandes amistades de la juventud no se replican en la vida adulta**. Los amigos de la infancia, los amigos del barrio, **son diferentes** de los amigos que uno hace después. Tienen una intensidad propia. Y, cuando esas amistades se rompen (por traición, por distancia, por la propia vida), **se rompen de manera única**. No te recuperas igual de un divorcio que de una traición de un amigo de la infancia. La película lo entiende perfectamente. Yo he hablado de esto en otros posts (los [amigos de toda la vida](/post/amigos-de-toda-la-vida), las [conversaciones largas sin reloj](/post/conversaciones-largas-sin-reloj)) y *Érase una vez en América* es **el tratado fílmico** de esa idea.
## Los puntos discutibles
Por honestidad: la película tiene **un punto polémico** que conviene mencionar y que yo mismo no termino de digerir.
Hay **dos escenas de violencia sexual** en la película (una con la mujer de un orfanato, otra con Deborah ya adulta) que son **muy difíciles de ver**. La crítica feminista las ha discutido durante décadas, con razón. Leone las filma de forma que **no son glorificadas**, claramente: el espectador entiende que son actos terribles, y la película no premia al perpetrador. Pero **están filmadas** y siguen siendo **incómodas de ver**.
¿Eso descalifica la película? Para mí, no. Pero sí **modifica** mi recomendación. No la recomendaría sin avisar, especialmente a un público joven o sensible. Y no la recomendaría sin contexto: es una película de 1984 que filma una era y una mentalidad de hace más de un siglo, y la moral de la época rodada y la moral de la época retratada son **distintas a las nuestras**. Eso no excusa nada; lo enmarca.
Hay quien, por estas dos escenas, **descarta** la película. Lo entiendo. Yo no llego a ese extremo, pero **respeto la posición**. La obra grande puede tener manchas. Y esas manchas, en una obra grande, **conviene reconocerlas en lugar de barrerlas**.
## A quién se la recomiendo
Se la recomiendo a:
- **Quien tenga paciencia con películas largas**. Esta no perdona la prisa.
- **Quien aprecia el cine contemplativo**, con planos largos, silencios, miradas, música.
- **Quien tenga interés por la historia americana del siglo XX** (la inmigración judía, la Prohibición, la corrupción política de los sesenta).
- **Quien quiera conocer a Leone más allá de los westerns**.
No se la recomiendo a:
- **Quien necesite ritmo rápido** y giros constantes.
- **Quien sea sensible a las escenas de violencia sexual** (ver advertencia anterior).
- **Quien quiera una historia clara y ordenada**. Esta es deliberadamente desordenada.
## Cómo verla
**Versión europea original de 3h49m**, con subtítulos. Doblada al español también está disponible y es decente, pero el inglés americano de los actores es parte del aroma de la película.
**No la encadenes con otra cosa**. No es una película para ver una tarde con doble sesión. Es una sesión única, larga, comprometida.
**Tarde de domingo larga, cuatro horas libres, móvil apagado, café preparado en la cocina, mantita si es invierno**. Receta exacta.
**Y, si la vas a ver con alguien**, asegúrate de que esa otra persona **también está dispuesta** al compromiso. Es una película que **se ve juntos en silencio**. Si la otra persona se duerme a la hora y media o saca el móvil, mejor verla solo.
## Lo que viene en la serie
El próximo es **el día 31**, la número 9. Cambio total de tono y de país: **Francia, año 2001, una mujer joven con un acordeón de fondo, una alcantarilla con una canica, un café de Montmartre**. Si lo adivinas, la prepara con un yogur de fresa. Si no, sorpresa el día 31.
Hasta entonces.
---
# Fuck you money V: evitar la inflación de estilo de vida
URL: https://javiervalencia.net/post/fuck-you-money-inflacion-estilo-de-vida
Imagina dos personas. Pedro y Marta. Ambos empezaron a trabajar con 1.500 € al mes y ahorraban un 20%, es decir 300 €. Diez años después, los dos ganan 4.000 €. Pedro sigue ahorrando 300 €. Marta sigue ahorrando el 20%, es decir 800 €.
A los veinte años de carrera, Pedro tiene unos 110.000 € invertidos. Marta tiene **el triple**. Misma carrera profesional. Mismos sueldos. Diferencia: Marta no dejó que el estilo de vida se comiera los aumentos.
Esto es la **inflación de estilo de vida** (*lifestyle creep*), y este pilar de [fuck you money según Joan Tubau](/post/fuck-you-money-joan-tubau) va de cómo blindarte contra ella.
## El mecanismo
La inflación de estilo de vida funciona así:
1. Ganas más.
2. Te lo «mereces».
3. Mejoras una cosa: cambias el coche, te mudas a un piso un poco más grande, comes fuera un día más a la semana, cambias el portátil de gama media a gama alta.
4. El gasto sube **menos que el ingreso**, así que parece que ganas. Pero solo parece.
5. A los seis meses, ese nuevo nivel es **el normal**. Ya no se siente como un lujo.
6. Llega el siguiente aumento. Repetir.
El efecto compuesto en sentido contrario. Cada peldaño parece pequeño. La escalera completa te aleja años del *fuck you money*.
## Por qué es tan tramposa
La inflación de estilo de vida es traicionera por tres razones psicológicas que Tubau articula bien:
**Primero, la adaptación hedónica**. El cerebro humano se acostumbra a casi cualquier nivel material en pocos meses. El coche premium que el primer mes te alegraba la salida del parking, al sexto mes ya es «el coche». Pero la factura del seguro, ITV y mantenimiento dura los siete años de financiación.
**Segundo, los costes asociados**. Cada mejora viene con una cola de costes que no aparecen el primer día:
- Piso más grande → IBI más alto, comunidad más cara, más muebles, más decoración, calefacción más cara.
- Coche premium → seguro más caro, ITV más cara, ruedas más caras, gasolina más cara (suelen ser más potentes), parking más caro.
- Barrio más caro → comer fuera cuesta más, las clases extraescolares cuestan más, el dentista cuesta más.
**Tercero, la trampa social**. Te has mudado a un piso de 300.000 €. Tus vecinos son ingenieros, médicos, abogados sénior. Sus hijos van al colegio privado. Compran ropa de marca. Sales con ellos a cenar y los menús empiezan en 60 € por cabeza. La presión de pertenencia hace que el estilo de vida medio del barrio se convierta en tu nuevo suelo.
Esto último es lo más difícil de combatir porque **no es decisión consciente**. Es el agua en la que nadas.
## La regla del 50% de Tubau
Tubau propone una regla práctica para domesticar la inflación de estilo de vida: del **cada aumento de sueldo, máximo el 50% va a mejorar tu estilo de vida; el otro 50% (mínimo) va al colchón**.
Es decir: si cobrabas 2.000 € netos y ahorrabas 400 (20%), y te suben a 2.400 €, lo natural sería pasar a ahorrar 480 €. La regla del 50% dice: **mete al menos 600 €**, los 400 de antes más la mitad del aumento (200).
Si lo haces consistentemente, tu tasa de ahorro **sube cada año** sin que sientas que renuncias a nada. Porque psicológicamente sí estás disfrutando del 50% del aumento. Solo no estás disfrutándolo entero.
A los cinco años de aumentos del 4-5% anual, tu tasa de ahorro pasa del 20% al 30-35% sin que hayas tenido que «apretarte el cinturón» ni un solo mes.
## Las trampas más caras del contexto español
En el contexto español, las inflaciones de estilo de vida más caras tienden a ser predecibles:
**1. La vivienda**
«Vamos a un piso un poco más grande». La hipoteca pasa de 800 € a 1.200 €. Pero la subida real no es solo eso: IBI sube unos 300 €/año, comunidad otros 200-400 €/año, derramas potenciales, muebles para llenar el espacio. La diferencia anual real está entre 6.000 y 8.000 €.
Tubau es muy claro: **antes de mover el piso, calcula el coste total durante diez años**, no la cuota mensual. Y compáralo con lo que ese mismo dinero invertido te daría.
**2. El coche**
Pasar de un utilitario a un SUV «familiar» fácilmente dobla el coste anual: seguro, depreciación, gasolina y mantenimiento. Y **lo usas para lo mismo**: llevar a los niños al cole, ir al súper, escaparte a la playa el fin de semana.
**3. Los hijos**
Aquí Tubau es especialmente prudente porque toca fibra. Pero los datos no mienten: las academias privadas, las extraescolares premium, los campamentos de verano en idiomas, los regalos de cumpleaños con piñata profesional, etc., han **inflado el coste de criar un hijo de clase media** entre un 30 y un 50% en una década. Mucho de eso no se traduce en mejores resultados educativos.
**4. La comida**
Pasar de cocinar cuatro días a la semana a cocinar uno parece pequeño, pero come 200-400 € al mes. Lo notas al año.
**5. Las suscripciones**
ChatGPT Plus, Claude Pro, GitHub Copilot, Notion, una newsletter, dos newsletters, Spotify familiar, Netflix premium, Disney+, HBO, Filmin, un gimnasio, un canal de YouTube premium... Es muy fácil acabar en 200-300 € al mes solo en suscripciones. **Revisa el extracto bancario una vez al año** y se ven con claridad.
## La estrategia del «no me llega»
Una técnica concreta que Tubau recomienda y a mí me funciona: **vive como si ganaras menos**.
Cuando te suben el sueldo, no avises a tu cuenta corriente. Que el aumento se vaya directamente del banco a la cuenta de inversión, sin pasar por la cuenta de gasto. **Tu cuenta corriente no se entera de que ganas más**. Tu cerebro tampoco. Y por tanto, tampoco tus gastos.
A los seis meses de hacerlo, el aumento ya «no existe» en tu vida diaria. Pero sí existe en tu cartera. Esa es la magia.
Esta es la versión personal de la lección del pilar II: **arquitectura, no fuerza de voluntad**. Si dependes de que cada mes decidas no gastar el aumento, perderás. Si automatizas que el aumento ni siquiera llegue a la cuenta donde gastas, ganas por defecto.
## Los gastos «buenos» que sí valen la pena
Importante: el pilar no dice que no gastes. Dice que **gastes en lo que de verdad valoras**, no en lo que el ascensor social te pide gastar.
Para mí, gastos buenos son:
- Comida de calidad en casa (mejor materia prima, no más cantidad).
- Una buena silla y un buen monitor (paso la vida sentado delante de ellos).
- Libros (todos los que quiera, sin culpa).
- Tiempo con las niñas: planes que las hagan vivir cosas, no juguetes que les regalen un viernes y olviden el lunes.
- Salud: gimnasio, médico, dentista, fisio si hace falta.
- Vacaciones más largas pero más sencillas (dos semanas en Málaga > cinco días en Bali con jet lag y maletas perdidas).
Lo que he ido quitando con el tiempo:
- Coches innecesariamente caros.
- Ropa de marca por marca.
- Restaurantes carísimos sin que el plato lo justifique.
- Gadgets nuevos solo porque salió la nueva versión.
- Casas más grandes de lo que necesitamos para vivir.
Esa lista no es prescriptiva. La mía es la mía. **Lo importante es que tú hagas la tuya**, y que la pongas a competir contra los aumentos de sueldo. Cuando llega el aumento, no se va automático al banco: se va automático **a tu lista de gastos buenos** o al colchón. Nunca a la lista de gastos por inercia.
## El test de los cinco años
Para cerrar, una herramienta de comprobación que cito mucho. Cada vez que estés tentado de subir un nivel de gasto, pregúntate:
> «¿Si dentro de cinco años me viera obligado a volver al nivel actual, sentiría que he perdido algo importante o sentiría que es lo normal?»
Si la respuesta es «sería lo normal», entonces el nivel nuevo no te dará felicidad duradera. La adaptación hedónica te llevará otra vez al punto de partida. Y mientras tanto, te habrás comido años de libertad financiera.
Si la respuesta es «sería una pérdida real» (por ejemplo, dejar de poder pagar la educación de tus hijos, perder el gimnasio que te mantiene sano), entonces sí merece la pena.
La inflación de estilo de vida es el pilar más psicológico de la serie. No tiene matemática complicada. Tiene autoconocimiento.
En el próximo post entramos en el sexto pilar: **tiempo > dinero**, donde Tubau cuestiona la idea de que un sueldo alto siempre sea un buen negocio.
---
# Las diez mejores películas de mi vida: 7. La lista de Schindler
URL: https://javiervalencia.net/post/top-10-peliculas-7-la-lista-de-schindler
Llevamos dos posts sobre películas que tocan el Holocausto: ayer mismo *La vida es bella*, hoy *La lista de Schindler*. Las dos no podrían ser más distintas. Una se acerca al horror desde la ternura, **la otra lo afronta directamente**. Y, sin embargo, las dos están en mi top diez. Eso, que pueda haber dos películas tan distintas sobre el mismo tema, las dos en mi lista, dice algo sobre **cómo el Holocausto requiere muchos lenguajes** para ser contado, y sobre cómo el cine puede operar con todos.
Esta es la séptima entrega de la serie sobre mis diez películas favoritas. La uno (Cinema Paradiso), la dos (Cadena perpetua), la tres (El padrino I y II), la cuatro (Casablanca), la cinco ([La vida es bella](/post/top-10-peliculas-5-la-vida-es-bella)) y la seis (Forrest Gump) están publicadas. Las cuatro restantes son la siete (esta), la ocho, la nueve y la diez. La serie va de la uno hacia abajo.
## Spielberg adulto

En 1993, Spielberg estrenó **dos películas el mismo año**. Una fue *Jurassic Park*, la película que más dinero recaudó del año, una superproducción de aventuras con dinosaurios digitales que cambió la industria de los efectos visuales. La otra fue *La lista de Schindler*, **una película en blanco y negro de tres horas y un cuarto sobre el Holocausto**. Es difícil pensar en dos películas más distintas hechas por el mismo director en el mismo año. Y es **una de las cosas más extraordinarias** de la carrera de Spielberg: que esa amplitud de registros existiera y que las dos películas funcionaran al máximo nivel cada una en su género.
Spielberg, hasta ese momento, era reconocido como **el director comercial más exitoso de Hollywood**: *Tiburón*, *E.T.*, *Indiana Jones*, *Encuentros en la tercera fase*. Hacía cine de espectáculo de altísima calidad, pero **no se le tomaba en serio** como autor. La crítica académica lo veía como un artesano del entretenimiento. Hasta *La lista de Schindler*. Esta película fue **el momento exacto** en que Spielberg dejó de ser solo el director de superventas y se convirtió en **uno de los grandes directores americanos** sin disquisiciones. Ganó siete Oscars, incluyendo mejor director y mejor película, los dos primeros que ganaba después de varias nominaciones.
Y lo más interesante de la película es que **es claramente una obra personal**. Spielberg, judío de familia askenazí, llevaba años queriendo hacerla. Había comprado los derechos de la novela de Thomas Keneally a principios de los ochenta. La fue posponiendo durante una década, primero porque la consideraba demasiado importante para su nivel del momento, después porque dudaba si era él el director adecuado. Cuando finalmente la rodó, **no cobró**: donó su salario a la creación del Shoah Foundation, una fundación dedicada a recopilar testimonios audiovisuales de supervivientes del Holocausto. Esa foundation ha grabado más de 50.000 testimonios desde entonces y es **una de las fuentes documentales más valiosas** sobre el siglo XX.
## El argumento, sin endulzar
La película cuenta la historia real de **Oskar Schindler**, un industrial alemán nazi, miembro del partido, mujeriego, alcohólico, oportunista, que en la Cracovia ocupada por los alemanes monta una fábrica de menaje militar usando mano de obra judía del gueto y, después, del campo de concentración de Plaszow. Schindler empieza el negocio **por puro lucro**: la mano de obra judía es barata, el contrato con la Wehrmacht es lucrativo, los oficiales nazis se pueden sobornar.
Pero, a medida que la guerra avanza y las matanzas aumentan, Schindler **cambia**. No de manera explícita: la película no te da una escena de revelación moral. Es **un cambio gradual**, hecho de pequeñas decisiones acumuladas. Empieza a proteger a sus trabajadores. Empieza a sobornar a oficiales para que no sean trasladados. Empieza a comprar más trabajadores en el mercado negro de seres humanos del campo. Y, hacia el final de la guerra, **gasta toda su fortuna personal y se endeuda hasta el cuello para salvar a más de mil judíos**, transfiriéndolos de Plaszow a una fábrica nueva en Brünnlitz, Checoslovaquia, donde simulan producción militar pero en realidad **están manteniéndolos vivos hasta el fin de la guerra**.
La famosa "lista" es **la lista mecanografiada por su contable y administrador judío, Itzhak Stern**, con los nombres de los trabajadores que Schindler ha negociado salvar. Es **literalmente una lista de personas que van a vivir**. La pronuncia el propio Schindler: "*esta lista es la vida*".
Esa es la espina dorsal. Pero la película no es solo sobre Schindler. Es sobre **el sistema** que rodea a Schindler: el gueto, las redadas, las ejecuciones, las deportaciones, el campo de Plaszow comandado por Amon Göth, las cámaras de gas. Y, por encima de todo, sobre **los judíos individuales** que pasaban por ese sistema. Spielberg, sabiendo que el espectador podía abrumarse con números abstractos, **filmó muchas pequeñas escenas individuales**: una mujer ingeniera asesinada por Göth porque le dio una opinión técnica acertada, un trabajador anciano que se quema en una fragua, un niño escondido en una letrina, una niña con abrigo rojo. **Cada plano cuenta una historia individual**. Eso es lo que hace que el horror sea soportable y a la vez devastador.
## Liam Neeson, Ben Kingsley y Ralph Fiennes

**Liam Neeson** como Schindler es **una elección perfecta**. Neeson tiene la altura y la presencia física para encarnar a un hombre carismático que entra en una habitación y la habitación se gira a mirarlo. Y tiene la **capacidad de transmitir lo no dicho**: durante toda la película, Schindler **no explica** sus motivaciones. Es Neeson, con la mirada, con los silencios, con los pequeños gestos de duda, **quien sostiene esa ambigüedad sin que el personaje se vuelva incomprensible**. La gran escena final, la de la despedida en el patio de la fábrica liberada, donde Schindler se derrumba y dice **"podría haber salvado a más"**, es uno de los momentos más recordados del cine, y Neeson lo hace **sin caer en el melodrama**: el llanto está, sí, pero contenido, casi como si el personaje se sorprendiera él mismo de estar llorando.
**Ben Kingsley** como Itzhak Stern, el contable judío que se convierte en el conciencia moral de Schindler, es **el segundo pilar**. Kingsley había ganado el Oscar diez años antes por Gandhi y aquí hace una interpretación de **contención total**: un hombre formal, educado, cauto, que en cada escena calcula con precisión qué decir, cómo decirlo y cuándo callar. La relación entre Stern y Schindler es **el corazón emocional** de la película: dos hombres que empiezan trabajando juntos por intereses opuestos (Schindler quiere ganar dinero, Stern quiere salvar trabajadores) y que, a lo largo de la guerra, **convergen en una sola intención**.
Y luego **Ralph Fiennes**, en el papel de su carrera, como **Amon Göth**, comandante del campo de Plaszow. Fiennes era casi desconocido cuando se le contrató. Spielberg lo eligió porque quería **un actor sin "pasado moral"** en el espectador, un actor cuya cara nadie asociara a roles previos. Y lo que Fiennes construye es **uno de los villanos más perturbadores del cine**, no por exuberancia, no por gritos (de los cuales hay), sino por **la forma en que filtra cualquier impulso humano a través de un sistema mental enfermo**. Hay una escena en la que Göth, desde el balcón de su villa que da al campo, **dispara con rifle a prisioneros al azar mientras desayuna**. La escena es **literalmente** así: dispara, sigue desayunando, dispara, mira al criado, sigue. Esa banalidad del mal es **lo que Hannah Arendt había descrito** años antes y que Fiennes encarna en pantalla con una precisión escalofriante.
Más perturbador todavía: la atracción confusa que Göth siente por su criada judía Helen Hirsch. Una atracción que él vive como **una vergüenza moral** (porque, en su sistema mental nazi, los judíos son subhumanos y desearlos sexualmente es **traición**). Esa contradicción interna que Fiennes interpreta sin aliviarla nunca **lo convierte en uno de los personajes más complejos del cine sobre nazis**. No es un nazi caricaturesco. Es un nazi **funcional**, criado en un sistema, brillante en la administración del horror, y a la vez **lleno de pequeñas grietas humanas que él rechaza**.
## El blanco y negro como decisión moral
La película está rodada **casi entera en blanco y negro**. Esa decisión es de Spielberg y del director de fotografía, **Janusz Kamiński** (polaco, en su primer trabajo grande con Spielberg, y a partir de ahí su director de fotografía habitual). La razón no es estética. Es **moral**.
Spielberg explicó después que **el Holocausto, en su mente y en la mente de su generación, era en blanco y negro**: las imágenes documentales, las fotografías de archivo, los testimonios visuales que él había visto creciendo. Filmar el Holocausto en color hubiera sido **falsificarlo**, en cierto sentido: hubiera sido **modernizarlo**, acercarlo al espectador con una cercanía que el horror no se merece. El blanco y negro **mantiene una distancia respetuosa**: te dice que **lo que ves ya pasó**, que **es historia**, que **no es ahora**.
Y, dentro del blanco y negro, hay **una escena en color**. La famosa: **la niña del abrigo rojo**. Una niña pequeña que vemos brevemente durante la liquidación del gueto de Cracovia, escondiéndose, buscando refugio, y a la que el espectador sigue con los ojos porque su abrigo, **rojo encendido**, es el único objeto coloreado en una secuencia de cinco minutos. Más adelante en la película, vemos **el mismo abrigo, también en rojo**, pero ya en un cuerpo, en una pila de cadáveres. **Es el abrigo el que reconocemos**, no la cara. El abrigo es **la pista que nos dice**: **esa niña que viste antes, esta vez se ve un cuerpo, está ahí**.
Esa decisión visual es **una de las más debatidas del cine**. Los detractores dicen que **rompe la coherencia visual** de la película y que es un truco emocional barato. Los defensores (yo entre ellos) decimos que **es exactamente lo contrario**: es **un recordatorio visual de que cada uno de los muertos era un individuo**, que los números abstractos del Holocausto contienen niñas con abrigos rojos. La decisión de Spielberg es **convertir a la niña en un símbolo personal**, no estadístico. Y eso, para mí, **funciona**.
Spielberg ha contado en entrevistas que **la idea le vino de una superviviente**, que en una conversación le contó una historia muy parecida: **una niña vestida de rojo, que había llamado la atención por la incongruencia visual, y que había desaparecido**. Spielberg trasladó esa imagen al cine **como homenaje**. La niña real, que se llamaba Roma Ligocka, **sobrevivió**: la película ha cambiado la imagen pero la inspiración era de la realidad. Roma escribió un libro autobiográfico años después, llamado *La niña del abrigo rojo*, en el que cuenta su propia historia.
## La banda sonora de Williams: la nota más triste
La banda sonora de **John Williams** para *La lista de Schindler* es, para mí, **la mejor banda sonora de su carrera**. Williams es el compositor más reconocido del cine americano: La guerra de las galaxias, Tiburón, Indiana Jones, E.T., Harry Potter, Superman. Todas memorables. Pero todas **épicas y heroicas**. *La lista de Schindler* es **lo opuesto**: una composición íntima, casi de cámara, dominada por **un solo de violín** ejecutado por **Itzhak Perlman**, uno de los más grandes violinistas del siglo XX.
El tema principal **es triste sin caer en sentimentalismo**. Es triste con **dignidad**. Es la tristeza de quien ha visto demasiado y ya no llora; **se queda mirando**. El violín de Perlman, judío israelí cuyo padre era polaco, hijo de supervivientes del Holocausto, **carga la pieza con un peso adicional** que ninguna otra grabación ha podido alcanzar. Hay grabaciones posteriores con otros violinistas, todas técnicamente buenas, todas más vacías que la original. Es un caso donde **el intérprete específico** define la pieza.
Williams compuso el tema y se lo enseñó a Spielberg con dudas. Le dijo "*esto necesita un compositor mejor que yo*". Spielberg le contestó "*ya lo sé, pero todos los compositores mejores que tú están muertos*". Williams se quedó. Y compuso lo mejor de su vida.
## Una nota sobre Roman Polanski y la lección que aprendí
Quien quiera ampliar el cine sobre el Holocausto debe ver, después o antes, ***El pianista*** (2002) de **Roman Polanski**, otra obra maestra del tema. Polanski, judío polaco superviviente del gueto de Cracovia (el mismo escenario de la primera parte de *Schindler*), había rechazado dirigir *La lista de Schindler* cuando Spielberg se lo ofreció. **Era demasiado personal** para él y no se sentía con fuerzas. Se reservó *El pianista* para una década después.
Las dos películas son **complementarias**. *Schindler* es la mirada de un alemán que salva. *El pianista* es la mirada de un judío que sobrevive. *Schindler* tiene un protagonista activo. *El pianista* tiene un protagonista que se esconde, casi sin actuar. Vistas juntas, **te dan dos lados de la misma realidad** y dos vías de cine sobre lo mismo.
Yo aprendí de *Schindler* algo que no había entendido antes: **que el cine sobre el horror no tiene que filmar el horror frontalmente para que el horror se sienta**. La mayoría de las muertes de la película se ven **de pasada, desde lejos, con la cámara siguiendo otra cosa**. La selección final del campo, la cámara de gas falsa que es ducha, los cuerpos en las pilas, las marchas. Spielberg **no se regodea en ninguna**. Te las muestra **lo justo** y se va al siguiente plano. Esa contención es **lo que hace que la película sea sobreviviente**: no te abruma con violencia explícita, te abruma con la **acumulación**.
## Lo personal: por qué entró en la lista
A los treinta y pocos vi *La lista de Schindler* por primera vez en VHS, y la ví **como película**. Me impresionó pero la coloqué en la categoría "obras maestras lejanas". A los cuarenta volví a verla, esta vez en una sala donde la habían reestrenado por aniversario, y **me pegó de manera distinta**. Me empezaron a pesar las escenas concretas, los personajes individuales, la idea del salvar **a uno**.
Porque, en el fondo, **eso es lo que enseña la película**: que el bien moral no es **abstracto** ni **sistémico**; es siempre **concreto**. Schindler no salva "a los judíos" como categoría: salva **a Itzhak, a Helen, a Marcel, a más de mil personas con nombre y apellido**. La película es exhaustiva en eso: las pone en la lista uno a uno, las nombra, las cuenta. **Cada nombre es una persona**.
Esa lección es de aplicación general. Cuando uno hace bien (donaciones, voluntariado, ayuda al amigo en mal momento), el bien siempre **es concreto**. Es **una persona, en un momento concreto, con una necesidad concreta**. La filantropía abstracta, las causas, los manifiestos, **están bien**, pero **el bien material lo hace una persona a otra**. Y eso es lo que Schindler hace. **Una a una**.
Y hay una segunda lección, más oscura. Schindler no era un buen hombre antes de la guerra. **Era un oportunista**, un mujeriego, un nazi de carné, un explotador. La película no le da una redención total. **Lo deja como era, con sus debilidades**, y le añade **un acto de bien grande, hecho con motivación cambiante**. Eso es importante: la película no te dice **"para salvar vidas hay que ser un buen hombre"**. Te dice "*incluso un mal hombre puede, en circunstancias extremas, salvar vidas, y eso, una vez hecho, vale lo que vale*". El bien moral **no requiere santos**. Requiere **gente que en el momento decisivo decida bien**. Y eso es accesible a cualquiera.
Esa es **la segunda gran lección** de la película. La primera era **el bien es concreto**. La segunda es **el bien lo puede hacer cualquiera**. Las dos juntas son **un retrato de qué es la moralidad ordinaria** que pocas obras de arte alcanzan.
## Las críticas razonables
Por honestidad, igual que con *La vida es bella*, hay críticas a *La lista de Schindler* que merecen reconocimiento.
**Crítica uno: la película pone a un alemán como protagonista de una historia sobre judíos.** Esta crítica, formulada por algunos historiadores e intelectuales judíos (Claude Lanzmann, autor del documental *Shoah*, fue uno de los más vocales), **tiene un punto**. La película hace que **el espectador se identifique con el salvador alemán**, no con los salvados. Las víctimas judías, aunque tienen escenas y caras, **no son los protagonistas narrativos**. Lanzmann argumentó que esa elección **invierte la mirada** que el cine sobre el Holocausto debería tener, y que los judíos siguen siendo **objetos** de la narrativa, no sujetos. La crítica es legítima, y la película tiene esa lectura. Yo respondo que **la película de los judíos como sujetos sí existe**: es *El pianista*, es *Hijo de Saúl*, es el documental *Shoah* mismo. *La lista de Schindler* hace **una cosa concreta**: cuenta la historia de un alemán que salva. Y esa también merece ser contada.
**Crítica dos: la última escena, con los supervivientes reales en el cementerio, es manipulación sentimental.** En la última escena de la película, ya en color y en presente, vemos a los **supervivientes reales** de la fábrica Schindler, ya ancianos, depositando piedras sobre la tumba de Schindler en Israel. Algunos críticos dicen que esa escena **chantajea emocionalmente** al espectador, que ya estaba conmovido por las tres horas anteriores. Mi posición: **es una de las escenas más bellas y dignas que ha rodado el cine**. **No chantajea**: agradece. Y muestra al espectador, **con caras reales**, que la lista no fue un truco narrativo: que esos hombres y mujeres **existieron**, **viven**, **tienen nietos**, **siguen vinculados a Schindler décadas después**. Es **el cierre exacto** que la película necesitaba.
**Crítica tres: Spielberg hace cine convencional incluso sobre el Holocausto.** Algunos críticos europeos (y americanos) han dicho que la película es "hollywoodiense" en el peor sentido: que tiene un héroe, un villano, una redención, una banda sonora emotiva, una estructura clásica de tres actos. Y que un tema como el Holocausto requeriría **un lenguaje más experimental**. Mi respuesta: **un lenguaje experimental** llega a una minoría. **Un lenguaje convencional** llega a millones. Y, dado que el objetivo de Spielberg era **transmitir el Holocausto a generaciones que nunca habían leído sobre él**, la elección formal **fue acertada**. Hay sitio para *Shoah* (10 horas, sin actores, casi sin música) y hay sitio para *Schindler*. Las dos son obras grandes. La una para los iniciados. La otra para todos.
## Cómo verla
**No la veas con prisa**. Dura tres horas y cuarto. **Te pide la tarde entera**.
**Versión original con subtítulos**. Hay diálogos en alemán, en yiddish, en polaco. La versión doblada los homogeniza al español y se pierde **una de las cosas que la película hace bien**: la babel lingüística del campo, la dificultad de comunicación entre víctimas y verdugos.
**Después de la película, deja un rato libre**. No la encadenes con otra cosa. Sal a la calle, camina, no abras el móvil. Lo que la película deja, lo deja en silencio. Si lo cierras inmediatamente con otro estímulo, **se evapora**.
**Y si tienes hijos adolescentes**, esta es **una de las películas que merece verse en familia** una vez en la vida. La conversación posterior **es el regalo de la película**. Lo que aprenden los hijos no es la película; es **lo que oyen contar a sus padres después**.
## A quién se la recomiendo
A todo el mundo. Esto es de las pocas películas que recomiendo **sin matices**.
Si solo te puedes ver una sobre el Holocausto en tu vida, que sea esta. Si te ves dos, ésta y *El pianista*. Si te ves tres, súmale *La vida es bella*. Si te ves diez, te abro un mundo aparte (te recomiendo *Shoah*, *Hijo de Saúl*, *El hijo de Saúl*, *El pianista*, *Au revoir les enfants*, *La zona de interés*, *La elección de Sophie*, *Holocausto* de la TV, *Anne Frank*).
## ¿Por qué siete y no más arriba?
La pregunta que me he hecho al ordenar la lista. *La lista de Schindler* es **objetivamente** una de las mejores películas que he visto. Probablemente, en términos de calidad técnica y peso histórico, **debería estar más arriba**. ¿Por qué no?
Porque, **como pasaba con Casablanca**, esta es una película que admiro **más que** quiero. Le tengo el respeto que merece, le reconozco la grandeza, le rindo el tributo. Pero **no es una película que ponga un domingo cualquiera para acompañar un café**. Es **una película que se ve** una vez cada cinco años, en sesión solemne. Y eso, en términos de top diez personal, la coloca en el séptimo puesto, no más arriba.
El top tres es de **películas que me llenan**. *La lista de Schindler* me **vacía**. Las dos cosas son legítimas. Las dos están en mi lista. Pero el orden no es accidente.
## Lo que viene en la serie
El próximo es **el día 30**, la número 8. Otra italiana (con esta van tres en mi top diez), otra película larga (con la de hoy serían dos largas seguidas), otra película sobre el paso del tiempo (tema recurrente en mi lista). Pero esta vez **el director es siciliano** y los protagonistas son **niños creciendo en el Lower East Side de Nueva York**, lo cual ya es pista. Si lo adivinas, ten preparada la flauta. Si no, sorpresa el día 30.
Hasta entonces.
---
# Fuck you money IV: diversificación de verdad
URL: https://javiervalencia.net/post/fuck-you-money-diversificacion
«Diversificar» es de las palabras más usadas y peor entendidas en finanzas personales. Para muchos consiste en tener cuentas en tres bancos. Para otros, en comprar veinte acciones distintas del IBEX. Ninguna de las dos cosas es diversificar. Y este pilar de [fuck you money según Joan Tubau](/post/fuck-you-money-joan-tubau) es importante precisamente porque es donde el español medio comete más errores.
## Qué es y qué no es diversificación
Diversificar es **reducir el riesgo de concentración**. Es decir, hacer que cuando algo en tu cartera baje, otra cosa suba o aguante, de forma que el conjunto fluctúe menos que sus componentes.
No es:
- Tener varios productos del mismo banco. (Si quiebra el banco, da igual cuántos productos tengas.)
- Tener veinte acciones del IBEX. (Si España tiene una crisis, todas se hunden a la vez.)
- Tener tres pisos en la misma ciudad. (Si baja el mercado inmobiliario local, los tres bajan.)
- Tener Bitcoin, Ethereum y Solana. (Todas correlacionan al 90% con BTC.)
Sí es:
- Repartir en **tipos de activo** distintos (acciones, bonos, oro, inmobiliario).
- Repartir en **geografías** distintas (EE. UU., Europa, emergentes).
- Repartir en **divisas** distintas (euro, dólar, libra, yen).
- Repartir en **plazos** distintos (líquido, medio plazo, largo plazo).
Cuanto menos correlacionados estén tus activos, mejor diversificación. Y la correlación importa más de lo que parece, porque las correlaciones **cambian con el tiempo y suben justo en las crisis**, cuando más las necesitas bajas.
## El sesgo casa-país, el error más caro
Si miras la cartera media de un inversor español, suele tener algo así:
- Piso en propiedad (España).
- Plan de pensiones (con sobrepeso de España y Europa).
- Acciones del IBEX (Santander, Telefónica, Iberdrola).
- Algún fondo «mixto» del banco (con sobrepeso de España y Europa).
- Cuenta corriente en euros.
**Todo en euros, todo en Europa, mucho en España**. Si España y Europa van mal, todo va mal a la vez. Esto es lo opuesto a diversificar.
El sesgo casa-país (*home bias*) es universal: los americanos tienen demasiado S&P 500, los japoneses demasiado Nikkei, los españoles demasiado IBEX. Pero **para los españoles es especialmente caro** porque España representa menos del 1% del PIB mundial y menos del 0,5% de la capitalización bursátil global. No tiene sentido económico que un español tenga el 30% o el 50% de su patrimonio invertido aquí.
Tubau no es radical: **vivir en España, comprar comida en España, pagar la hipoteca en España ya es un sesgo enorme hacia España que no puedes evitar**. Si encima tu cartera también está sobreponderada a España, te has metido un riesgo doble.
La solución: **que tus inversiones líquidas sean lo más globales posible**. El MSCI World del pilar III ya hace gran parte del trabajo: solo ~3-4% es España. Casi todo es EE. UU., Europa desarrollada, Japón, Reino Unido, Canadá.
## Activos: la pirámide de riesgo
Una cartera diversificada de verdad reparte entre tipos de activo con comportamientos distintos:
**Líquido (0-10%)**
- Cuenta corriente, depósitos a un día, fondos monetarios.
- Cobra inflación o menos. Su función es **estar disponible**, no rentar.
**Renta fija (10-40%)**
- Bonos del Estado, bonos corporativos de calidad, fondos indexados de bonos globales con cobertura a euro.
- Suelen aguantar cuando la bolsa cae, aunque en 2022 los dos cayeron a la vez (rareza histórica).
**Renta variable (40-80%)**
- Acciones globales vía fondos indexados.
- Volátil a corto, ganadora a largo. El motor de la cartera.
**Inmobiliario (variable)**
- Vivienda habitual (que no es inversión, es uso).
- REITs si quieres exposición líquida al sector.
**Oro / refugio (0-5%)**
- Físico o ETF de oro (con cuidado de la fiscalidad en España).
- Función: proteger en escenarios extremos. No rentar.
**Apuestas asimétricas (0-5%)**
- Cripto, capital riesgo, startups, lo que sea.
- Dinero que **puedes perder entero sin que afecte a tu plan**.
El porcentaje exacto depende de tu edad, horizonte y tolerancia al riesgo. Pero **la mezcla importa más que la elección de cada producto individual**. Estudios clásicos como Brinson-Hood-Beebower atribuyen más del 90% de la varianza de retornos a la **asignación de activos**, no a la selección concreta de valores.
## Geografía: el mundo no es solo Estados Unidos
Hay una tentación creciente, sobre todo entre inversores jóvenes, de poner todo en S&P 500. Ha rendido espectacular las últimas dos décadas. ¿Por qué diversificar a mercados peores?
Tubau no es entusiasta de esa simplificación. Argumentos:
- **Reversión a la media**: los mercados que mejor lo hacen una década suelen ser de los peores la siguiente. EE. UU. dominó los 2010-2020. En los 2000-2010 fue una década casi plana. En 1990-2000 brilló Japón antes y EE. UU. después. No hay garantía de que EE. UU. siga siendo el ganador eterno.
- **Concentración sectorial**: el S&P 500 hoy es ~30% tecnología, dominado por siete empresas. Si esas siete tienen un problema regulatorio, el índice entero sufre.
- **Concentración cambiaria**: todo en dólares. Si el dólar se debilita frente al euro, pierdes en euros aunque la bolsa suba en dólares.
El MSCI World ya es 65-70% EE. UU., así que tampoco hay que obsesionarse. Pero **prefiero MSCI World o ACWI antes que S&P 500 puro**. El diferencial de rentabilidad esperada es pequeño y el de riesgo, no.
## Divisas: el peligro silencioso
Otro punto que pasa desapercibido. Si vives en España y todos tus ingresos son en euros, **tener algo de patrimonio en otras divisas es una diversificación gratuita**.
El fondo MSCI World ya te da exposición natural a dólar, libra, franco suizo, yen, etc. (un ~70-80% no-euro). No suele hacer falta complicarse más. Pero tener todo el patrimonio en euros —cuenta corriente euros, depósito euros, piso en España, fondo de RV europea— te deja vulnerable a una crisis del euro o a una inflación persistente local.
Tubau es escéptico de la cobertura cambiaria (*currency hedging*) en fondos de RV: el coste de la cobertura suele comer la rentabilidad y a largo plazo la divisa se mueve menos de lo que parece. **Sí defiende cobertura en renta fija**, donde la rentabilidad esperada es baja y la volatilidad cambiaria puede destruirla.
## Plazos: ¿cuándo necesitas el dinero?
El último eje de diversificación es el temporal. No todo el dinero tiene el mismo horizonte:
- **Reserva de emergencia (6 meses)**: líquido siempre, sin volatilidad.
- **Objetivo a 1-3 años** (coche, obra, máster): renta fija corta, conservador.
- **Objetivo a 3-10 años** (vivienda nueva, educación hijos): mezcla 50/50.
- **Objetivo a 10+ años** (independencia financiera): casi todo en RV indexada.
La función de cada cubo es diferente. Mezclarlos es de los errores más caros: meter el dinero del coche que necesitas en seis meses a RV global puede dejarte sin coche.
## El sobrediseño que mata
Una advertencia para los que les gusta el Excel. Tubau, igual que Bogle, alerta contra **complicar la cartera por gusto**.
He visto carteras minoristas con 18 fondos: MSCI World, MSCI Emerging, MSCI Small Cap, dos REITs (uno europeo, uno global), oro físico, oro ETF, bono soberano alemán, bono USA, bono emergente, fondo monetario, cripto, capital riesgo, sectorial salud, sectorial tecnología, factor value, factor momentum... y al final del año, **rinden parecido al MSCI World a secas, pero con cinco veces más esfuerzo**.
Diversificar bien es **encontrar el punto donde añadir otro activo deja de bajar el riesgo significativamente**. Para casi todo el mundo ese punto está en **2-4 fondos máximo**:
- 70-85% MSCI World o ACWI.
- 15-30% Renta fija indexada global con cobertura a euro.
- Opcionalmente, 5% oro / REIT / otra cosa si te divierte.
Más allá de ahí es **hobby, no diversificación**.
En el próximo post entramos en el quinto pilar: **evitar la inflación de estilo de vida**, donde toca lidiar con el enemigo más sutil de todos, el que aparece justo cuando empiezas a ganar más.
---
# Fuck you money III: inversión pasiva en fondos indexados de bajo coste
URL: https://javiervalencia.net/post/fuck-you-money-inversion-pasiva-fondos-indexados
Si has aplicado los [dos pilares anteriores](/post/fuck-you-money-ahorro-agresivo-tasa-de-ahorro), ahora tienes un problema mejor que el de antes: tienes **dinero ahorrado todos los meses** que no sabes dónde poner. La cuenta corriente lo pierde frente a la inflación. El depósito a un día apenas la cubre. El piso lo descartas porque ya tienes uno (o porque no quieres ataduras). ¿Qué queda?
La respuesta de Tubau, y la respuesta de casi cualquier asesor financiero serio que no cobre por venderte producto, es la misma desde los años setenta: **fondos indexados de bajo coste**.
## La idea, en una frase
Un fondo indexado no intenta «ganar al mercado». Lo replica. Si compras un fondo que sigue al MSCI World, compras una participación proporcional en las 1.500 empresas más grandes del mundo desarrollado. Si esas empresas en agregado suben, el fondo sube. Si bajan, baja.
Lo importante es lo que **no** hace: no contrata gestores carísimos para elegir acciones, no especula, no cambia de estrategia, no intenta «adivinar» el siguiente Apple. Por eso cobra una comisión anual del 0,2-0,3% en lugar del 1,5-2% que cobran los fondos activos.
Esa diferencia del 1,5% anual parece poco. En treinta años, sobre una cartera de 100.000 €, son **del orden de 60.000 € adicionales en tu bolsillo** solo por no pagar comisiones. La magia del interés compuesto, aplicada al revés: lo que pagas también compuesta contra ti.
## La evidencia que aplasta el debate
Este es uno de los pocos temas en finanzas donde los datos son tan contundentes que ya no hay discusión seria. SPIVA, el informe que S&P publica cada año comparando fondos activos contra sus índices de referencia, lleva más de veinte años contando lo mismo:
- **A 1 año**, alrededor del 55% de los fondos activos pierden contra el índice.
- **A 5 años**, sube al 75%.
- **A 10 años**, al 85%.
- **A 20 años**, al 90-95%.
Y de los pocos que ganan, no son los mismos cada vez. **El que fue el mejor en 2024 no es el mejor en 2025**. Encontrar al gestor estrella ex ante es estadísticamente imposible.
Conclusión: **si pagas a alguien por gestionar tu dinero, lo más probable es que lo haga peor que un robot tonto que replique el índice**. Por eso Tubau, igual que Bogle, igual que Buffett, igual que cualquiera que mira números sin filtros gremiales, recomienda lo mismo.
## La cartera mínima viable
Tubau no es maximalista en este punto. Su recomendación más sencilla, para alguien que no quiera complicarse, es **una sola posición**:
- **Fondo indexado global** (MSCI World o MSCI ACWI). Comisión ~0,2%.
Eso es todo. Una posición. 1.500-3.000 empresas, 23 países desarrollados (más emergentes en el ACWI), un solo producto, un solo botón.
Para quien quiera matizar un poco, la siguiente capa es:
- **80% MSCI World / ACWI** (renta variable global).
- **20% Renta fija global** (fondo indexado de bonos, con cobertura a euro).
Y para los que ya están cerca de necesitar el dinero, esa proporción se inclina más hacia renta fija para reducir volatilidad.
Por encima de ahí ya entra **sobreoptimización**: REITs, mercados emergentes separados, small caps, etc. Casi siempre **complica más de lo que aporta**. Hay estudios que muestran que la cartera más compleja del minorista medio rinde **peor** que la sencilla, porque el coste de fricción y los errores de rebalanceo se comen el supuesto valor añadido.
## Aportar periódicamente, no apostar
La técnica que más insiste Tubau es **DCA, *dollar cost averaging***: aportar la misma cantidad todos los meses, pase lo que pase en el mercado. Suena aburrido. Es exactamente lo que debe ser.
DCA funciona porque elimina el peor enemigo del inversor minorista: **uno mismo**. Si intentas «entrar en mínimos» y «salir en máximos», estadísticamente fallas. Los estudios de comportamiento (Morningstar publica el suyo cada año) demuestran que el inversor medio **pierde un 1,5-2% anual frente a su propio fondo** por entrar y salir en mal momento. Más de lo que paga en comisiones.
La regla práctica:
1. **Aportación automática mensual** al fondo indexado.
2. **No mires el saldo** más de una vez al trimestre.
3. **No vendas nunca por noticias**. Ni guerra, ni elecciones, ni crisis, ni «esta vez es diferente». Esta vez nunca es diferente.
El comportamiento óptimo es **comprar siempre, vender nunca** hasta que necesites el dinero para vivir. Veinte o treinta años más tarde.
## Dónde contratar (España, 2026)
Las opciones razonables en el contexto español actual:
- **Indexa Capital**: gestor automatizado, carteras diversificadas, comisiones totales en torno al 0,55-0,65%. Lo más cómodo para quien no quiere tocar nada.
- **MyInvestor**: banco propiedad de Andbank, comercializa fondos indexados Vanguard y Amundi con comisiones bajas (~0,3% TER). Mejor coste, pero requiere más decisión propia.
- **Trade Republic / DEGIRO / IBKR**: brokers internacionales para ETFs. Más control y aún más barato, pero peor fiscalidad en España (los ETFs no tienen el traspaso fiscal que sí tienen los fondos).
La fiscalidad pesa. En España los **fondos de inversión tradicionales** permiten traspasar de un fondo a otro sin tributar plusvalías (solo tributas cuando reembolsas y sacas el dinero al banco). Los **ETFs no**. Por eso, para horizontes largos, muchos prefieren MyInvestor o Indexa antes que IBKR.
## La psicología, otra vez
Lo más difícil de la inversión pasiva no es entenderla. Es **soportarla**.
Vas a ver caídas del 30-50% del valor de tu cartera. Probablemente dos o tres veces en los próximos veinte años. Cada una se va a contar como «la peor crisis desde 1929». Cada una va a venir acompañada de gurús diciendo que el modelo está roto, que esta vez es diferente, que hay que salir.
Tubau es muy claro: **el que aguanta gana**. La gente que estaba invertida en 2000 y aguantó hasta 2024 multiplicó por entre 3 y 5 su dinero. La gente que vendió en 2008 y volvió en 2014 se perdió la mejor década bursátil de la historia.
Por eso el ahorro en colchón (pilar II) es tan importante: **si tienes 6 meses de gastos en líquido, no necesitas vender en pánico**. Y si no necesitas vender, la cartera hace lo que tiene que hacer.
## El error que sí cuesta caro
Hay un error que en este pilar es realmente caro y conviene blindar: **el cripto-casino disfrazado de inversión**.
Tubau distingue muy claramente:
- Si el 5% de tu patrimonio quieres ponerlo en Bitcoin como exposición a un activo no correlacionado, es defendible.
- Si entras y sales de altcoins cada semana, eres un trader. Y los traders minoristas pierden dinero en agregado. Los datos de la SEC son inequívocos: más del 80% de cuentas de trading retail terminan en pérdidas.
La inversión pasiva es deliberadamente aburrida. Su única virtud es que funciona. Cualquier intento de hacerla emocionante es la antesala de perderlo todo.
En el próximo post entramos en el cuarto pilar: **diversificación**, donde matizamos por qué «todo al MSCI World» no es del todo cierto para alguien que vive en España y cobra en euros.
---
# Las diez mejores películas de mi vida: 6. Forrest Gump
URL: https://javiervalencia.net/post/top-10-peliculas-6-forrest-gump
Hay películas que se quieren con cierta vergüenza. *Forrest Gump* es la mía. Cuando la mencionas en una conversación de cinéfilos, los hay que arrugan la nariz: "*demasiado sentimental*", "*conservadora*", "*lo opuesto a Pulp Fiction*, *que era la otra candidata al Oscar y debería haber ganado*". Y cada uno tendrá su parte de razón. Pero cuando yo veo *Forrest Gump*, **caigo entero**, todas las veces. Y, treinta años después, **sigue funcionando**. Por eso ocupa la sexta posición de esta serie.
Esta es la sexta entrega. Si estás llegando por primera vez, recomendable leer primero la [número uno](/post/top-10-peliculas-1-cinema-paradiso). La serie va de la uno hacia abajo. Las cinco anteriores están publicadas y enlazadas entre sí.
## La pluma volando

La primera y última escena de *Forrest Gump* es **una pluma blanca flotando**. Empieza en lo alto del cielo de Savannah, baja despacio, va virando con la brisa, se posa al lado de un banco en el que está sentado un hombre con un traje claro y una caja de bombones en el regazo. Y, al final de la película, la misma pluma se va flotando hacia arriba otra vez, mientras el hombre (ahora visto desde abajo) deja de mirarla y se va.
Esa pluma **es la imagen de toda la película**. Es la metáfora visual de **una vida que no se controla del todo, que va donde la lleva la brisa, pero que es libre en su movimiento**. Forrest Gump, el personaje, no es el dueño de su vida en el sentido convencional. No tiene **proyectos**, ni **plan de carrera**, ni **estrategia personal**. Le pasa lo que le pasa, y él responde a cada cosa con honestidad, sin reflexionar mucho sobre el sentido último. La pluma se posa, la pluma se va.
La pregunta filosófica que la película plantea, sin pretensión académica, es **si esa forma de vivir es mejor o peor que la otra**. La otra es la de Jenny, **el otro personaje central**: una mujer que sí quiere controlar su vida, que va detrás de ideas, de movimientos políticos, de relaciones difíciles, de drogas, de escenarios, de huidas. Forrest y Jenny, las dos vidas paralelas que se cruzan a lo largo de cuatro décadas, **son la dialéctica completa de la película**.
Y, sin que la película tome partido explícito, **sí toma partido sutil**. Forrest, al final, **está mejor**. No por inteligencia, no por mérito, no por estrategia: simplemente, **está mejor**. Jenny ha muerto. Forrest es padre. Lo que queda de Jenny vive en el niño que cría Forrest.
¿Eso es **una defensa conservadora** de la vida sencilla frente a la vida ambiciosa? Para muchos, sí, y desde ahí se ha criticado a la película desde su estreno. Yo tengo otra lectura, más amable: **no es una defensa de lo sencillo frente a lo ambicioso; es una defensa de la coherencia interna frente a la dispersión**. Forrest hace siempre lo que cree que toca, sin desviaciones. Jenny **hace siempre lo contrario de lo que quiere realmente**, en un patrón compulsivo de autodestrucción. La película no premia la sencillez: **premia la coherencia**. Y eso es una cosa distinta.
## El argumento, sin spoilers nuevos para nadie
Por si entras frío, aunque a estas alturas casi todo el mundo conoce el contorno: Forrest Gump es un hombre del sur de Estados Unidos, nacido en los años cuarenta, con un coeficiente intelectual que la película sitúa por debajo del umbral oficial pero que no le impide vivir y trabajar. La película es **el recorrido de su vida**, contado por él mismo desde un banco en una parada de autobús de Savannah, a quien quiera escucharle (varios desconocidos van pasando por el banco). Forrest cuenta, sin orden estricto y entre bombones, su vida: la niñez con cojeras y aparatos en las piernas, el descubrimiento de que podía correr más rápido que nadie, la beca universitaria de fútbol americano, la Guerra de Vietnam, el regreso, el bambú de fortuna en una empresa de gambas, el inversor accidental en Apple, las décadas posteriores, **y a Jenny**, la chica del bus escolar que se sienta a su lado y a la que ama toda la vida.
Es **una crónica personal de la segunda mitad del siglo XX americano** desde el punto de vista de un hombre que no termina de entender lo que está pasando alrededor pero que igualmente está ahí, en medio de los acontecimientos. Esa lectura "histórica" de la película (Forrest como cápsula del tiempo paseándose por la historia reciente de su país) es **una de las cosas mejor resueltas del guion**.
## Tom Hanks: la mejor interpretación de su carrera

Tom Hanks tenía una carrera consolidada antes de *Forrest Gump*: comedias de los ochenta, *Big*, *Despertares*, *Filadelfia* (Oscar el año anterior, 1993). Pero **es Forrest Gump la que lo coloca en el lugar especial donde aún está hoy**: el de actor americano por antonomasia, el de **el James Stewart de su generación**.
Lo que hace Hanks con Forrest **es una de las grandes interpretaciones del cine americano**. No por la imitación: el acento sureño, la cadencia lenta, los gestos. Eso lo hace bien pero no es lo extraordinario. Lo extraordinario es que **construye al personaje desde la dignidad, no desde la lástima**. Forrest no es un personaje al que el espectador deba mirar **desde arriba**, con condescendencia, como **un pobre tonto del pueblo**. Es un personaje al que el espectador termina mirando **a la altura**, e incluso a veces **desde abajo**, porque su forma de tomar decisiones revela cosas que los listos no entendemos.
Hanks juega con eso. Tiene escenas en las que Forrest dice algo que parece tonto y que, a la luz de los eventos posteriores, resulta haber sido **más sabio que toda la sabiduría convencional**. La famosa frase de su madre, **"la vida es como una caja de bombones: nunca sabes lo que te va a tocar"**, parece simpleza. Pero, dicha en el contexto y la cara de Forrest, **funciona**. Es **filosofía operativa**, no consigna.
Y hay momentos donde Hanks **se contiene completamente**. La escena en la que Jenny le pide en una cama "*hazme el amor*" y Forrest tiembla por dentro pero apenas reacciona por fuera. La escena del entierro de Jenny. La escena en la que descubre que tiene un hijo. La escena del primer encuentro con su hijo. **En todas ellas, Hanks no actúa "emocionado"**: actúa **lo que el personaje siente, traducido al lenguaje del personaje**. Y Forrest no llora gritando. Llora despacio, casi sin mover la cara.
Es una lección de **respeto al personaje**: no usar al personaje para que el actor luzca, sino servir al personaje incluso cuando el papel pide silencio.
## Robin Wright y la tragedia de Jenny
Robin Wright como Jenny es **el otro pilar** de la película, y la actuación menos comentada de las dos. Jenny es **un personaje difícil**: una mujer víctima de abusos en la infancia (la película lo cuenta de pasada pero con claridad suficiente), que crece intentando huir de su pasado pasando por todas las contraculturas de los sesenta y setenta (hippy, drogas, política radical, cantante de bar nudo, relaciones tóxicas). El personaje podría haber caído fácilmente en el cliché. Wright lo salva **dándole una mirada de profunda tristeza en cada escena**, incluso cuando ríe.
Hay una escena, en una habitación de hotel en Washington, donde Jenny se asoma al balcón y mira hacia abajo. La película no subraya nada, pero **el espectador entiende qué está pensando**. Y entiende, sin que se diga, que es **una mujer que carga con demasiado peso para una sola vida**. Esa escena, sin diálogo, es una de las más duras de la película. Y la hace Wright sin lágrimas, sin gestos, **solo con la espalda**.
Lo más interesante del personaje de Jenny no es su trayectoria sino **cómo encuentra paz al final**. Vuelve al pueblo, vuelve con Forrest, se sienta a leer en el porche, se sienta con su hijo, se sienta debajo del árbol del que se caía de niña. **No por madurez intelectual, sino por agotamiento**. Y la película no la juzga. La acoge. Y eso, en una época donde el discurso dominante era "*lucha siempre por tus sueños*", es **valiente**: la película dice que **a veces volver al sitio del que querías huir es la mejor decisión**.
Eso, a los cuarenta y muchos, con una vida hecha, con tres hijas, en la Costa del Sol, **me llega**. Yo también he hecho la traveseira del éxito profesional fuera y la vuelta a casa, en mi versión más modesta. La película de Jenny y su vuelta a Greenbow, Alabama, **no es ajena a esa experiencia**.
## La banda sonora: el disco recopilatorio de tu juventud
Una mención obligada: la banda sonora compuesta original es de **Alan Silvestri**, con un tema principal **simple, tres notas de piano, repetido con variaciones**, que volvió a la moda los temas minimalistas en cine. El tema funciona como la pluma: liviano, repetitivo, hipnótico.
Pero la banda sonora "real" de la película es **una recopilación de canciones de los sesenta, setenta y ochenta**, que acompañan los distintos capítulos de la vida de Forrest. Elvis, Bob Dylan, Joan Baez, Creedence Clearwater Revival, The Doors, Jimi Hendrix, Lynyrd Skynyrd, los Beach Boys. **Cada época histórica viene con su música**. Y para cualquiera que haya vivido esas décadas (o que las haya conocido a través de sus padres), **es un viaje sentimental** además de un viaje cinematográfico.
Esa banda sonora **se hizo doble álbum y vendió millones**. Y, en cierta medida, **definió cómo se montaban las películas con bandas sonoras compiladas en los noventa**. Quentin Tarantino había hecho algo parecido con *Pulp Fiction* (la otra gran candidata del año) pero en otro registro. Cameron Crowe lo perfeccionó con *Casi famosos*. Pero la mecha la había prendido Zemeckis con *Forrest Gump*.
## La proeza técnica
Hay una capa de *Forrest Gump* que ha envejecido **sorprendentemente bien**: la técnica. La película usa **efectos digitales para insertar a Tom Hanks en imágenes históricas reales** (con presidentes Kennedy, Nixon, Johnson), para borrar la pierna de Gary Sinise como teniente Dan, para crear el plumón inicial. En 1994, esos efectos eran **rompedores**. Treinta años después, **siguen siendo creíbles**, lo cual no se puede decir de la mayoría de efectos de la misma época (muchos han envejecido fatal: ver *Twister*, por ejemplo).
Robert Zemeckis era ya, en ese momento, **el director con más experiencia en mezclar imagen real y efectos digitales** (venía de *Quién engañó a Roger Rabbit* y de la trilogía de *Regreso al futuro*). En *Forrest Gump* hizo el salto a serio: efectos al servicio del personaje, no del espectáculo. Eso es lo que ha hecho que la película envejezca bien. **No se ve "vieja"** cuando la vuelves a poner en 2026.
## La cuestión Vietnam
Una de las partes más comentadas de la película es **el bloque de Vietnam**. Forrest es enviado al frente como soldado, sobrevive a una emboscada, salva a varios compañeros (incluyendo al teniente Dan, encarnado por **Gary Sinise** en lo que sigue siendo posiblemente su mejor papel), recibe la Medalla de Honor del Presidente. Y luego vuelve a Estados Unidos, donde el contexto es **la oposición creciente a la guerra**, los movimientos pacifistas, los hippies.
La película **toma una posición ambigua** sobre Vietnam. Por un lado, **retrata al soldado americano de Vietnam con respeto**, sin caer en el estereotipo del veterano traumatizado violento (aunque el teniente Dan sí encaja en parte en ese arquetipo). Por otro lado, **muestra a los manifestantes pacifistas como gente legítima**, con sus razones. Y, en el medio, **Forrest, que ni opina ni juzga**. Eso es lo que ha enfadado a parte de la crítica desde el estreno: **la película se niega a tomar partido**, lo cual a unos les parece prudente y a otros les parece **una cobardía moral**.
Mi lectura es **la primera**. La película no tiene que tomar partido sobre Vietnam: Vietnam no es su tema. Su tema es Forrest. Y Forrest no toma partido sobre Vietnam **porque Forrest no toma partido sobre casi nada**. Eso es coherente con el personaje. Si la película hubiera metido en boca de Forrest un discurso pro-bélico o anti-bélico, **habría traicionado al personaje**. Su silencio político es **su política**.
Lo que sí toma la película es **un partido humano**: que los soldados que volvieron de Vietnam **merecen respeto** sea cual sea tu posición política. Eso, en 1994, no era trivial. Y el personaje del teniente Dan, su rabia, su recuperación, su matrimonio final con una mujer asiática, **es una de las arcos secundarios mejor hechos del cine americano de los noventa**.
## ¿Es una película conservadora?
Esta es **la gran pregunta crítica** sobre *Forrest Gump*. Que la película ha sido leída como conservadora desde su estreno es **un hecho**: defiende la familia, defiende el ejército, defiende el trabajo duro, defiende la fidelidad, **y muestra a los movimientos contraculturales como caóticos y autodestructivos**. Esa es una lectura legítima.
Pero hay **otra lectura**. La película es generosa con todos los personajes: Jenny, que es todo lo contrario a Forrest, recibe la misma cantidad de empatía y de cariño narrativo. El teniente Dan, que es un veterano lleno de rabia, recibe respeto y curación. Bubba, el amigo afroamericano de la unidad, **es uno de los personajes más entrañables** de la película. La amistad entre Forrest y Bubba **rompe la línea racial** sin hacer un discurso sobre ello. La madre de Forrest, soltera, criando a un hijo solo en el sur en los años cincuenta, **es una figura de fuerza increíble**. Y la película no juzga a Jenny **ni siquiera cuando muere**.
Esa generosidad, esa **falta de juicio moral**, es lo que me hace pensar que **la lectura "conservadora" de la película es parcial**. Sí, la película valora ciertas cosas más que otras (la coherencia, la lealtad, la familia, la dignidad del trabajo) que se asocian al pensamiento conservador. Pero **valora a todos los personajes por igual**, sean cuales sean sus elecciones. Y eso, en términos políticos, **no es conservadurismo**, **es humanismo**.
Es la misma lectura que hago, por cierto, de *El padrino*: que **valorar la familia** no es lo mismo que **promover un orden social específico**.
## Lo personal: lo que me ha enseñado
A los treinta y pocos, cuando vi por primera vez *Forrest Gump*, me parecía sobre todo **un cuento estadounidense**: con presidentes, banderas, sentimentalismo. La disfruté pero la situé lejos.
A los cuarenta y muchos, **la veo distinta**. La veo como **un cuento sobre la coherencia**. Sobre cómo se construye una vida cuando uno no es brillante, no es ambicioso, no es estratégico, y aun así puede tener una vida llena. Forrest **no tiene plan**. Va respondiendo a lo que le pide cada momento. Y, mirado al final, **ha tenido una vida más rica que la mayoría de gente "exitosa"** que conoce.
Eso, ahora, me reconforta. Porque yo, en mi escala mucho más modesta, también he intentado vivir así: **respondiendo a cada momento sin un gran plan, intentando hacer bien lo que toca, intentando estar presente con quien hay que estar presente, intentando ser fiel a las pocas cosas que me importan**. Y eso, en 2026, mirando atrás, me parece más sabio que las trayectorias planificadas de muchas personas alrededor.
Forrest no me ha dado un modelo de vida, claro. Pero sí me ha dado **un permiso**: el de no avergonzarme de no haber tenido grandes ambiciones, el de no sentir que he "perdido" porque otros han llegado más lejos en términos de marca, fama o dinero. **No todo el mundo necesita ser Steve Jobs**. Algunos podemos ser Forrest Gump. Y eso, mirando alrededor, no es mal sitio donde estar.
## La crítica más dura: ¿por qué Forrest siempre tiene suerte?
Una crítica que ha durado treinta años: **Forrest tiene demasiada suerte**. Sale ileso de Vietnam, gana medalla, conoce presidentes, se hace rico por casualidad, encuentra a su madre antes de que muera, encuentra a Jenny en los momentos clave, descubre que tiene un hijo, hereda el negocio del bambú. **La película encadena casualidades buenas** sin acuse de recibo.
Mi respuesta: **sí, y precisamente eso es la idea**. La película es **una fábula**, no un retrato realista. Y, como toda fábula, **acumula casualidades buenas en su protagonista** para iluminar un mensaje. Si Forrest fuera un personaje realista con suerte estadística normal, **la película no funcionaría**, porque entonces solo veríamos a un hombre con discapacidad intelectual al que la vida pasa por encima. **La película necesita la suerte** para que su mensaje sobre la coherencia interna se pueda transmitir. Es la lente que permite ver lo que la película quiere que veas.
Si esto te parece tramposo, no te va a gustar la película. Si te parece **decisión estilística legítima de una fábula**, te va a llegar.
## A quién se la recomiendo
Se la recomiendo a:
- **Quien haya tenido alguna vez complejo de inferioridad** (académico, profesional, social). Forrest es el antídoto.
- **Quien lleve años queriendo a alguien que no termina de quererte como tú querrías**. La historia de Forrest y Jenny es la radiografía de eso.
- **Quien tenga un hijo o hija**. La escena de Forrest viendo a su hijo en el salón por primera vez es **una de las mejores escenas de paternidad del cine moderno**.
- **Quien necesite acordarse de cómo era la cultura popular americana antes de internet**. Esta película es **una cápsula del tiempo perfecta**.
No se la recomiendo a:
- **Quien sea hipersensible al sentimentalismo de Hollywood**. La película tiene su cuota, sin disimular.
- **Quien viva mal la mezcla de ficción y realidad histórica**. Si te chirría ver a Tom Hanks dándole la mano a Kennedy con efectos digitales, no la disfrutarás.
- **Quien venga buscando una película sobre Vietnam en serio**. Para eso están *Apocalypse Now*, *La chaqueta metálica*, *Platoon*. *Forrest Gump* usa Vietnam como capítulo, no como tema.
## Cómo verla
**Doblada o subtitulada, da igual**. Por una vez en esta serie, no me sorprende. La doblada al español es decente y el acento sureño de Hanks, aunque memorable en versión original, no es decisivo para la película.
**No la veas con prisas**. Dura dos horas y media largas. Te pide entrega.
**Si la vas a ver con un hijo o hija**, está bien a partir de los doce o trece años. Hay temas adultos (drogas, violencia de guerra, abusos en la infancia retratados sin explicitar) pero la película los aborda de forma no traumática. Es **una buena puerta de entrada al cine americano de los noventa**.
**Y, si la has visto, vuélvela a ver a otra edad**. Te va a sorprender lo distinta que es a los cincuenta de como era a los veinte.
## Lo que viene en la serie
El próximo es **el día 29**, la número 7. Cambio de tono drástico: una película sobre el Holocausto, **esta vez sin comedia**, dirigida por uno de los grandes directores americanos vivos. **Casi una hora más larga que *Forrest Gump***, **en blanco y negro**, con **una escena en color** que es de las más famosas del cine moderno. Si lo adivinas, sí. Si no, sorpresa el día 29.
Hasta entonces.
---
# Fuck you money II: ahorro agresivo y tasa de ahorro
URL: https://javiervalencia.net/post/fuck-you-money-ahorro-agresivo-tasa-de-ahorro
Si el [primer pilar](/post/fuck-you-money-vivir-por-debajo-de-tus-posibilidades) era reducir gastos, este segundo va de **cuánto reduces**. Y la magnitud importa, porque la cifra que de verdad determina cuándo llegas a tener *fuck you money* no es tu sueldo. No es tu patrimonio absoluto. Es tu **tasa de ahorro**: el porcentaje de tu ingreso neto que cada mes acaba fuera de la cuenta corriente.
## La fórmula que cambia la perspectiva
Hay una tabla circulando por internet desde que [Mr. Money Mustache](https://www.mrmoneymustache.com) la popularizó en 2012, y Tubau la cita a menudo. Asume rentabilidad real del 5% (después de inflación) y la regla del 4% para los retiros. Le quitamos toda la magia y nos quedamos con la versión simplificada:
| Tasa de ahorro | Años trabajando para tener un año cubierto |
|---------------:|--------------------------------------------:|
| 10% | 9 años |
| 25% | 3 años |
| 50% | 1 año |
| 70% | 5 meses |
Esa columna es lo que cambia el juego. Con una tasa del 10% trabajas nueve años por cada año de libertad. Con una tasa del 50%, trabajas un año por cada año de libertad: relación 1:1. Con un 70%, **trabajar cinco meses te paga el siguiente año entero**.
Y lo más interesante: estos números **no dependen de tu sueldo absoluto**. Da igual que ganes 1.500 € o 15.000 €. Lo que cuenta es qué porcentaje de eso sales de tener que gastarte el mes que viene.
## Por qué la tasa absoluta es engañosa
«Yo ahorro 500 € al mes» es una frase sin información. Si los ganas vale la pena celebrarlo; si los ganas de un sueldo de 5.000 €, son un 10% raquítico que te lleva al primer escalón de la tabla.
«Yo ahorro el 35% de mi neto», en cambio, te dice todo lo que hace falta. **Es independiente del nivel de vida**, **es escalable** (si subes sueldo y mantienes la tasa, el ahorro absoluto sube en proporción), y **es comparable** entre personas con vidas muy distintas.
Por eso Tubau machaca la tasa, no el valor en euros. La tasa es un hábito; el valor absoluto es solo su consecuencia.
## ¿Qué tasa pone Tubau como objetivo?
Su rango realista es del **30 al 50%** del ingreso neto. Las cifras concretas que repite:
- **20%** es el mínimo razonable. Por debajo de eso, llegar a *fuck you money* en menos de 30 años es casi inviable.
- **30-40%** es el rango de gente con ingresos medios que ha apretado bien los gastos.
- **50%** ya entra en el territorio FIRE-light: 17 años para llegar a la independencia total.
- **>60%** es prácticamente solo posible con ingresos muy por encima de la media o con vida monástica.
A diferencia del FIRE puro americano, que apuesta por el 70-80%, Tubau es más realista para el contexto español. Aquí los sueldos son más bajos, los impuestos más altos, y la vivienda en zonas urbanas se come una parte considerable. Aun así, defiende que **el 30% es alcanzable para la mayoría de la clase media** si se aplican los pilares de la serie con disciplina.
## Cómo calcular tu tasa real
Es más fácil de lo que parece y suele dar resultados desalentadores la primera vez:
```
Tasa de ahorro = (Ingresos netos - Gastos totales) / Ingresos netos
```
Donde:
- **Ingresos netos**: lo que entra de verdad en tu cuenta después de IRPF y SS. No el bruto.
- **Gastos totales**: TODO lo que sale de la cuenta y no acaba en inversión, deuda buena (amortización de hipoteca) o reserva. Incluido vacaciones, regalos, imprevistos.
El error más común es contar solo los gastos «buenos» y olvidar los anuales (IBI, seguros, vacaciones). Coge los últimos doce meses de extracto bancario y haz la cuenta sobre números reales, no sobre lo que crees que gastas.
Mi tasa los últimos años ha oscilado entre el 25 y el 35%, con caídas en los años de cambio de piso y subidas en los años estables. No estoy en el FIRE-light. Estoy en lo «razonable». Y aun así, en una década, eso son varios años de gastos cubiertos por colchón.
## El ahorro agresivo no es esfuerzo de voluntad
Aquí es donde Tubau introduce su distinción más útil. Para él, la diferencia entre la gente que ahorra el 10% y la que ahorra el 40% **no es disciplina**. Es **arquitectura**.
La persona que ahorra el 10% lo hace después de gastar. Lo que sobre. Su arquitectura es:
> Ingreso → gasto → (si queda algo) ahorro.
La persona que ahorra el 40% **lo automatiza el día 1**:
> Ingreso → ahorro automático → gasto sobre lo que queda.
No hay willpower involucrada. No hay que decidir cada mes. El dinero sale antes de que te enteres de que ha llegado. Lo que ves en la cuenta es lo que tienes para gastar. Y el cerebro humano se adapta a eso con sorprendente facilidad.
Las herramientas concretas para automatizar:
- **Transferencia programada** el día 2 a una cuenta separada (idealmente otro banco, para que no la veas en el extracto principal).
- **Aportación periódica a fondo indexado** vía un *neobroker* (Indexa, MyInvestor, Trade Republic, etc.). Aquí entra el tercer pilar de la serie.
- **Amortización extra de hipoteca** si tienes una con tipo razonable. Es ahorro forzoso a tasa garantizada.
Cualquiera de esas tres mueve dinero antes de que pase por la cuenta corriente. Y eso, en agregado, es lo que dispara la tasa.
## El sesgo del ingreso variable
Una nota para autónomos, freelancers y gente con bonus o comisiones. La tasa de ahorro hay que calcularla **sobre el ingreso medio anual**, no sobre el mes bueno. La trampa clásica del autónomo es vivir mejor los meses gordos y comerse el colchón los flacos.
La receta de Tubau aquí es brutal: **fija tu nivel de gasto al mes peor del año**, y trata el resto como variable a ahorrar. Si tu peor mes fue 1.800 € de ingresos, vive con 1.500 € los doce meses. Lo que llegue por encima va al colchón.
Es duro al principio. Pero **iguala el flujo de caja mental** con el de un asalariado, que es psicológicamente más sostenible y financieramente más sano.
## Dónde guardar el ahorro mientras decides qué hacer con él
Una nota práctica antes de cerrar. El ahorro no puede vivir en la cuenta corriente, porque pierde frente a la inflación y porque la tentación de gastarlo es alta. Pero tampoco puede irse entero a inversión, porque necesitas un colchón líquido para emergencias.
La división razonable:
- **Reserva de emergencia**: entre 3 y 6 meses de gastos en una cuenta remunerada o depósito a un día. Que rente algo, que esté disponible. **No invertida en renta variable**, porque el día que la necesitas suele coincidir con que la bolsa haya bajado.
- **Resto del ahorro**: a inversión a largo plazo. Aquí entra el siguiente post de la serie.
Sin ese colchón previo, cualquier imprevisto (paro, enfermedad, derrama de la comunidad) te obliga a vender inversiones en el peor momento. Y eso destroza el efecto compuesto que tanto te ha costado construir.
En el próximo post entramos en el tercer pilar: **inversión pasiva en fondos indexados de bajo coste**, que es donde el ahorro acumulado deja de ser dinero parado y empieza a trabajar por ti.
---
# Corazón de atún encebollado en El Gallo: el despiece menor que merece la pena pedir (cierre de la serie)
URL: https://javiervalencia.net/post/pena-flamenca-el-gallo-corazon-de-atun-encebollado
Octava y última entrega de la serie sobre la cocina de Paco Flores en la peña flamenca El Gallo, en Las Lagunas de Mijas. Después del [pisto de boletus y huevo campero](/post/pena-flamenca-el-gallo-pisto-de-boletus-y-huevo-campero), cerramos la serie con un plato que **dice más sobre la cocina del sitio que cualquier otro de la pizarra**: el **corazón de atún encebollado**.
No es un plato fácil de explicar a alguien que no lo conoce. Cuando uno lee "corazón de atún" en una pizarra, hay dos reacciones típicas: la del que ya sabe lo que es y se le ilumina la cara, y la del que arruga el ceño y pasa al siguiente plato. Este post es para los segundos. Y, también, para confirmar a los primeros que en El Gallo lo hacen como debe hacerse.
## Qué es el corazón de atún
Hay que romper un mito de entrada: cuando hablamos de **corazón de atún**, hablamos del **corazón anatómico** del pescado. El órgano. El músculo cardíaco. No es una metáfora, no es la "parte central" del lomo. Es **casquería de pescado**, en el sentido literal: aprovechar las partes del animal que no son los cortes nobles.
El corazón de atún es:
- **Pequeño** en relación al pescado (un atún de 100 kg tiene un corazón de unos 200-300 g).
- **Denso, muy magro, casi sin grasa**. Textura más cercana a una víscera que a un lomo: firme, prieta, con fibra apretada.
- **Sabor muy concentrado** a pescado, marítimo, profundo. No es sabor sutil. Es **directo**.
En la cocina del sur, especialmente en la zona de **Cádiz**, donde la cultura del atún de almadraba lleva siglos asentada, las vísceras y despieces menores del atún (corazón, hueva, mojama, morrillo, parpatana, facera) tienen tradición. La almadraba es un sistema de pesca milenario que aprovechaba el atún entero, no solo los lomos. Con el aprovechamiento integral nacieron preparaciones para todas las partes, y el corazón es una de las que ha llegado mejor hasta hoy.
En zonas no costeras, o en zonas costeras con cultura del atún más reciente, este corte casi no se ofrece. Verlo en pizarra **fuera de Cádiz** es señal de algo: de que la cocina mira más allá de lo evidente y aprovecha producto que pocos piden.
## Atún rojo vs atún de aleta amarilla: aquí, aleta amarilla
Aclaración importante porque en el primer post de la serie cometimos una imprecisión que toca corregir. **El atún que llega a El Gallo no es atún rojo**, sino **atún fresco de aleta amarilla** (yellowfin, *Thunnus albacares*). Son dos especies distintas, ambas válidas, con perfiles de sabor y precio muy diferentes:
- **Atún rojo** (*Thunnus thynnus*): el atún de almadraba clásico, con grasa intramuscular alta, ventresca espectacular, precio premium. Muy estacional (mayo-junio).
- **Atún de aleta amarilla**: más magro, más uniforme, color rojo claro, sabor algo más suave. Disponible casi todo el año en lonja, precio razonable. Es el atún que más circula en pescaderías y restaurantes españoles del nivel medio-alto.
El corazón, en el atún de aleta amarilla, mantiene esa **textura prieta y sabor concentrado** del que hablábamos. No tiene la grasa del atún rojo (los cortes premium del rojo son los cortes grasos), pero el corazón es músculo magro en cualquier especie.
Ojo: **fresco de aleta amarilla** no es lo mismo que **congelado de aleta amarilla**. Aquí es fresco, de lonja, no congelado.
## La técnica del encebollado
El encebollado es una preparación de la cocina del sur que consiste en **pochar cebolla a fuego lento, durante mucho tiempo, hasta que caramelize y quede dulce**. La cebolla pasa por varios estados:
1. **Translúcida** (5-10 min): la cebolla pierde el blanco crudo, se vuelve transparente.
2. **Dorada clara** (15-25 min): empieza a tomar color tostado en los bordes.
3. **Caramelizada** (40-60 min): la cebolla está oscura, deshecha, dulce, casi convertida en mermelada.
El encebollado serio busca el tercer estado. **Sin prisa.** El error más común es subir el fuego para acortar tiempo: la cebolla se quema por fuera y queda cruda por dentro, amarga, sin dulzor. La cebolla bien encebollada **se hace en fuego bajo, removiendo de vez en cuando, durante una hora aproximadamente**.
Una vez la cebolla está en su punto, se incorpora el corazón de atún cortado fino (lonchas de pocos milímetros, en contra de la fibra para que no quede correoso) y se da un golpe de fuego corto. **Solo un golpe de fuego**. El corazón es músculo magro y prieto: se cocina rápido y, si se pasa, se queda como goma. Lo razonable es **un par de minutos**, lo suficiente para que el corte se caliente y absorba el sabor de la cebolla, no más.
El plato se sirve en cazuelita o en plato hondo, con la cebolla en la base y las lonchas de corazón encima. Ligado por el aceite de la cebolla y, en algunas versiones, un toque de **vinagre de Jerez** que aporta acidez para cortar el dulzor.
## Cómo lo borda Paco
La versión de Paco tiene los detalles donde tienen que estar:
- **Cebolla bien encebollada**, dulce sin estar quemada, deshecha en boca pero no convertida en pasta.
- **Corte del corazón fino y uniforme**, en contra de la fibra. Cada loncha se mastica con facilidad, no como un trozo correoso.
- **Punto de cocción del corazón**: tibio, casi crudo en el centro, perfectamente caliente fuera. Justo donde tiene que estar.
- **Toque de vinagre de Jerez** discreto que abre el plato al final del bocado.
- **Pan al lado** para mojar la cebolla. Imprescindible.
Lo que **no hace**, y eso también dice cosas: no añade pimienta en exceso, no maquilla con perejil picado, no monta presentación moderna. Plato de cazuela honesto, donde se ve lo que hay.
## Por qué hay que pedirlo
Aquí va la tesis del post. **El corazón de atún encebollado es uno de esos platos que hay que pedir cuando aparece en pizarra**, por dos razones:
1. **Es el plato que mejor define la honestidad de una cocina.** Trabajar despieces menores de atún (corazón, parpatana, facera, hueva) requiere conocimiento, requiere proveedor que los ofrezca, requiere estar dispuesto a explicar a clientes que no los conocen. Una cocina que pone esto en pizarra está renunciando al camino fácil del lomo o la ventresca y diciendo "yo trabajo el atún entero, no solo los cortes que se venden solos". Esa es **cocina con criterio**.
2. **No lo encuentras en cualquier sitio.** Fuera de Cádiz y de algunas costas concretas, este plato es raro. Verlo en pizarra en Mijas es razón suficiente para pedirlo. Si te gusta, lo añades a la lista corta de platos a buscar siempre. Si no, ya sabes algo de tu paladar.
## Maridaje: tres opciones
El corazón de atún encebollado es un plato denso, dulce-salado, con notas profundas. Pide bebida que limpie y acompañe sin tapar.
1. **Manzanilla en rama o fino de Jerez**, frío. Como con casi todo lo del marco de Jerez sobre cocina del sur: la salinidad y la sequedad limpian la grasa de la cebolla y dejan el paladar listo para el siguiente bocado. Maridaje canónico.
2. **Vino tinto joven con acidez**. Una garnacha joven o una mencía limpia. Aquí el tinto sí entra (a diferencia de los buñuelos o la alcachofa) porque el plato tiene cuerpo de carne casi, no de pescado blanco.
3. **Cerveza de fermentación baja, fría**. Lager mediterránea, pilsner correcta. La amargura limpia.
**Lo que evitaría**: blancos muy aromáticos (sauvignon blanc, gewürztraminer), espumosos dulces, vinos de barrica larga.
## Cómo pedirlo
1. **De los primeros del bloque salado**, antes de los platos contundentes. Es plato denso pero no excesivo en cantidad.
2. **En tapa o en ración según mesa.** En tapa para uno o dos; en ración cuando hay tres o cuatro y la curiosidad es general.
3. **Pregunta si hay**, porque puede no aparecer en pizarra todos los días. Es plato que depende de pesca y disponibilidad.
## Para terminar la serie
Cierro aquí la serie sobre la peña flamenca El Gallo y la cocina de Paco Flores. Han sido ocho entregas, contando esta:
1. [Presentación de la serie y recorrido por la pizarra](/post/pena-flamenca-el-gallo-mijas-presentacion-de-la-serie).
2. [Alcachofa con brandada de bacalao](/post/pena-flamenca-el-gallo-alcachofa-con-brandada-de-bacalao).
3. [Secreto ibérico](/post/pena-flamenca-el-gallo-secreto-iberico).
4. [Solomillo al Pedro Ximénez](/post/pena-flamenca-el-gallo-solomillo-al-pedro-ximenez).
5. [Buñuelos de bacalao](/post/pena-flamenca-el-gallo-bunuelos-de-bacalao).
6. [Bastones de berenjena con miel de caña](/post/pena-flamenca-el-gallo-bastones-de-berenjena-con-miel-de-cana).
7. [Pisto de boletus y huevo campero](/post/pena-flamenca-el-gallo-pisto-de-boletus-y-huevo-campero).
8. **Corazón de atún encebollado** (esta entrega).
Si tuviera que resumir la serie en una sola frase, sería esta: en una zona donde la cocina pública se ha instalado en la mediocridad cómoda del menú del día con foto, **la peña flamenca El Gallo defiende un modelo distinto**, donde la pizarra cambia, el chef firma cada plato, los despieces menores aparecen junto a los cortes premium, y el cliente que entra a pedir tapa con caña termina aprendiendo cosas que no esperaba.
Es lo que hace que un sitio se vuelva **sitio de volver**, y no sitio de ir una vez. Y es por eso que esta serie ha existido, y por lo que sigo recomendando, sin reservas, la peña flamenca El Gallo a cualquiera que pase por Las Lagunas de Mijas.
Lo que vendrá en el blog en próximas semanas, en este registro gastronómico, dependerá de qué siga apareciendo en la pizarra y de qué nuevos sitios merezcan, en mi opinión, esta clase de atención. Si te ha interesado la serie, suscríbete al RSS y te van llegando las novedades.
Y si vas a la peña, ya lo sabes: deja que te aconsejen, pregunta por los formatos, pide variedad, y reserva si vas viernes o sábado por la noche. Si llegas a las nueve sin reserva, esperarás seguro.
---
# Fuck you money I: vivir por debajo de tus posibilidades
URL: https://javiervalencia.net/post/fuck-you-money-vivir-por-debajo-de-tus-posibilidades
Este es el primer pilar de la serie sobre [fuck you money según Joan Tubau](/post/fuck-you-money-joan-tubau). Y el más simple de enunciar: **gasta menos de lo que ingresas**. Ya está. Si lees esto y aplicas eso solo, la serie ya te ha servido.
Pero el motivo por el que no es trivial no es matemático. Es psicológico.
## La asimetría que nadie te cuenta
Cuando hablamos de finanzas personales casi todo el foco va a los ingresos: cómo subir el sueldo, cómo cambiar de trabajo, cómo crear un *side hustle*. Y está bien, pero ignora una asimetría fundamental: **subir ingresos es difícil y depende de terceros; bajar gastos es difícil pero depende solo de ti**.
Tu jefe puede no darte el aumento. Tu cliente puede no aceptar la subida de tarifas. El mercado puede no premiar tu valor real. Pero **nadie** puede impedirte que canceles una suscripción, que comas en casa tres días más a la semana o que renueves el coche cada diez años en lugar de cada cuatro.
Tubau insiste mucho en esto: el lado del gasto es **tu zona de control**. No es que dé igual ganar más —ayuda muchísimo— pero la mayoría de la gente que llega a tener *fuck you money* lo hace porque controló los gastos primero, no porque ganara una fortuna.
## La diferencia entre frugalidad y tacañería
Aquí es donde la filosofía se separa del estereotipo. Vivir por debajo de tus posibilidades **no significa vivir mal**. Significa vivir **deliberadamente**.
Hay dos preguntas a hacerse con cada gasto:
1. **¿Me da esto algo que valga lo que cuesta?**
2. **¿Es esto algo que de verdad quiero o es algo que me han enseñado a querer?**
La primera es economía. La segunda es psicología. Y la psicología pesa más.
Ejemplos concretos del contexto español:
- **Coche.** Tener coche en Madrid o Barcelona, con buen transporte público, cuesta entre 4.000 y 7.000 € al año entre seguro, ITV, gasolina, parking, mantenimiento y depreciación. Si lo usas dos veces por semana, sale a 50-70 € el viaje. ¿Vale el coche eso? Si trabajas desde casa, probablemente no. Si vives en un pueblo sin transporte, sí.
- **Comer fuera.** Salir a comer en Málaga cuesta 15-25 € por cabeza un día normal. Si lo haces cinco veces a la semana, son 350-600 € al mes. Si lo haces dos, son 140-240 € y además **disfrutas más** porque deja de ser rutina.
- **Suscripciones.** Netflix, Disney+, HBO, Spotify, gimnasio, ChatGPT, Claude, dos newsletters de pago, una nube, una VPN. Cuarenta euros aquí, doce allá. Al año son fácilmente 1.500-2.000 € que pasan desapercibidos.
Ninguna de estas cosas está mal en sí misma. Lo que está mal es no haberlas elegido. La diferencia entre **gastar en lo que te importa** y **gastar por defecto** es la diferencia entre frugalidad y derroche.
## El presupuesto que no es un presupuesto
Tubau es escéptico de los presupuestos detallados (los famosos Excel con cien filas y diez categorías). Su receta es más simple: **paga primero al colchón, después al banco, y solo después a ti**.
En la práctica:
1. **Domicilia el ahorro.** Que la nómina entre, y que el día 2 de cada mes salga automáticamente la parte de ahorro a una cuenta separada (idealmente a un broker o cuenta de fondos). Que **no la veas**.
2. **Paga las facturas fijas.** Hipoteca/alquiler, luz, agua, internet, teléfono, comunidad, seguros.
3. **El resto es lo que tienes.** Sin Excel. Sin tabla. Lo que quede en la cuenta corriente es lo que puedes gastar este mes.
Esta inversión de orden —ahorrar antes de gastar, no ahorrar lo que sobre— es probablemente el truco más poderoso de la filosofía completa. Lo que sobra **nunca sobra**. Pero si el dinero ya está fuera de la cuenta, no se gasta.
## La trampa del «merecérselo»
Hay una frase que oirás cada vez que alguien justifica un gasto grande: *me lo merezco*. El piso más grande. El coche premium. El reloj. Las vacaciones de catorce días en Bali.
Tubau le da un capón a esa frase. No porque no la merezcas. Probablemente la mereces. Es porque «merecérselo» **no es un criterio financiero**, es un mecanismo de defensa para no pensar.
La pregunta correcta no es «¿me lo merezco?» (la respuesta siempre es sí) sino:
> «¿Si me lo regalaras en lugar de tener que pagarlo, lo aceptaría?»
Para la mayoría de gastos premium la respuesta es no, porque sabes que viene con coste de mantenimiento, complica la vida, exige tiempo. El BMW no es solo el BMW, es el seguro, la ITV cara, el miedo al parking, las ruedas a 1.200 € el juego.
Vivir por debajo de tus posibilidades es **eliminar todo lo que aceptarías si te lo regalaran pero no comprarías si tuvieras que pagarlo dos veces**.
## El número que me funciona a mí
Yo no tengo un porcentaje fijo. Lo que sí tengo es una regla blanda: **gastar nunca más del 60-70% del neto**, y meter el resto a inversión y reserva.
Esa cifra incluye hipoteca (que para mí es ahorro forzado, no gasto puro), tres hijas, alimentación de los cinco, transporte y los pequeños lujos que sí elijo (chiringuito un par de veces al mes, suscripciones específicas, libros). Lo demás —ropa de marca, gadgets nuevos por inercia, restaurantes caros sin motivo— ha ido cayendo solo, sin que lo eche en falta.
Quien parte de cero y vive en el centro de Madrid con un alquiler que se come el 50% del sueldo, probablemente este pilar es el más doloroso. La respuesta de Tubau es contundente: **si la vivienda te impide ahorrar, te has equivocado de vivienda**. Mover la palanca de los gastos pasa muchas veces por mover la palanca de la geografía.
## El efecto compuesto, también en los hábitos
Lo más interesante de este primer pilar no es el dinero que ahorras el primer año. Es lo que pasa después.
Cuando llevas dos años viviendo con el 70% de lo que ganas, **te has acostumbrado**. Ese 30% ya no te lo «pierdes», porque no existía en tu radar. Si te suben el sueldo, lo natural es seguir igual y meter el aumento entero al colchón.
Eso es lo que aguanta toda la filosofía. No es un sacrificio temporal para llegar a una cifra. Es un cambio en el set point de tu vida. Cuando llevas cinco años viviendo así, ya no es renuncia, es identidad.
Y desde esa identidad, **decir «que te jodan» empieza a ser una opción real**.
En el próximo post de la serie entramos en el siguiente pilar: **ahorro agresivo y tasa de ahorro**, donde el porcentaje empieza a ser el indicador clave, no la cifra absoluta.
---
# Las diez mejores películas de mi vida: 5. La vida es bella
URL: https://javiervalencia.net/post/top-10-peliculas-5-la-vida-es-bella
Hay películas que son difíciles de defender en una conversación de café porque la gente que las ha visto se divide en dos bandos irreconciliables. *La vida es bella* es de esas. La quinta de mi top diez se gana ese sitio precisamente por lo que la divide: **cuenta el horror del Holocausto desde la ternura**, lo cual a unos les parece la mejor cosa que se ha hecho en cine sobre el tema y a otros les parece una trivialización indignante. Mi posición es la primera y voy a dedicar este post a defenderla. Si vienes de la segunda escuela, te invito a leer hasta el final.
Esta es la quinta entrega de la serie. La uno (Cinema Paradiso), la dos (Cadena perpetua), la tres (El padrino I y II) y la cuatro (Casablanca) están ya publicadas. La serie va del puesto uno hacia abajo. Hoy, la cinco. Si entras frío, recomendable empezar por la [uno](/post/top-10-peliculas-1-cinema-paradiso).
## La premisa imposible

*La vida es bella* tiene **la premisa más arriesgada del cine de los noventa**. Un padre italiano, judío, llega a un campo de exterminio nazi con su hijo de cinco años. Para evitar que el niño se aterrorice y muera (o, peor, se vea destrozado por dentro), **el padre le convence de que todo lo que está ocurriendo es un juego**. Un juego enorme, con puntos, con reglas, con un premio final. Que ganarán si el niño se esconde cuando le toca, si no llora cuando le toca, si guarda silencio cuando le toca. Y que, si aguantan, **se llevarán un tanque de verdad** al final.
Esa premisa, contada así, suena a fracaso seguro. Es la frase típica de "*¿en serio van a hacer una comedia sobre el Holocausto?*". Y, sin embargo, **no es eso lo que la película hace**. La película hace algo mucho más sofisticado: **no es una comedia, es una tragedia que un padre intenta convertir en comedia para uso exclusivo de su hijo**. El espectador ve las dos cosas a la vez: la realidad terrible del campo, y la versión "infantil" que el padre teje encima para que el niño no la vea. Esa **doble narración** es lo que la convierte en una obra mayor.
Roberto Benigni, el director y protagonista, es **un cómico italiano** que en los años ochenta se había hecho famoso por una comedia muy física, casi de slapstick. Quien le veía en *El monstruo* o en *Johnny Stecchino* no podía imaginarse que iba a dirigir una película sobre el Holocausto. Y, sin embargo, hizo lo que **muchos consideramos su mejor obra**.
## La estructura en dos mitades
La película se divide claramente **en dos mitades de duración casi idéntica**, y entender esa estructura es entender por qué funciona.
**La primera mitad** es una **comedia romántica italiana clásica**. Guido (Benigni) es un joven judío que llega a Arezzo en los años treinta, intenta abrir una librería, se enamora de Dora (Nicoletta Braschi, esposa real de Benigni), una maestra prometida en matrimonio con un funcionario fascista. Guido la conquista a base de gags físicos, casualidades planificadas, fantasía. Es **una primera hora ligera, llena de luz toscana, de aceite de oliva, de risas, de movimiento**. La cámara baila, el ritmo es alto, los chistes son de cómico clásico italiano.
**La segunda mitad** es **el campo de exterminio**. Guido, su hijo Giosuè y Dora son deportados. Llegamos a un campo no identificado por nombre (la película deliberadamente no nombra Auschwitz, aunque obviamente lo está representando). Hombres y mujeres separados. Vida en barracones. Trabajo forzado. Frío. Hambre. Selecciones. **Y, dentro de todo eso, el juego del padre con el hijo**.
La transición entre las dos mitades es **brusca y deliberada**. La primera mitad es tan luminosa que cuando llega la segunda **te golpea como bofetada**. Hay quien ha criticado este contraste como manipulador. Para mí es exactamente al revés: **es esa luminosidad anterior la que hace que el horror sea horror**. Si la primera hora fuera oscura, lo que viene después sería previsible y menor. La primera hora **te enamora de los personajes**. Y la segunda **te los pone en peligro**, sin que tengas tiempo de prepararte.
## Por qué la primera mitad es esencial
He oído a gente decir "*la película estaría mejor si fuera solo la segunda mitad*". Eso es **un error de lectura**.
La primera mitad **no es relleno** ni es una concesión a quien no quiera ir al horror sin tomar carrera. Es la mitad que **construye al personaje de Guido lo suficiente** para que su comportamiento en el campo nos resulte creíble. Sin la primera mitad, **Guido en el campo es un loco**. Con la primera mitad, **Guido en el campo es Guido**: el mismo hombre que en su librería se inventaba escenas para impresionar a Dora, el mismo que se inventaba personajes para hacer reír al niño antes de la guerra, el mismo que **trataba la realidad como material maleable**. Cuando llega al campo, no cambia: usa la misma herramienta (la imaginación) en condiciones extremas.
La primera mitad nos enseña que Guido **es un fabulador profesional**. Por eso, cuando le toca fabular en el sitio más oscuro del mundo, sabe hacerlo. **No es una excentricidad**. Es lo que él es.
Esto, además, **plantea una idea hondísima**: que la capacidad de imaginar, de inventar, de hacer reír, de transformar la realidad en relato, **no es una frivolidad**, no es algo "menor que las cosas serias". Es **una herramienta de supervivencia**. La risa, la fantasía, el chiste, el juego, todo eso, en un campo, **puede salvar a un niño**. Esa idea, defendida sin pretensiones por una película popular, vale más que cinco ensayos académicos.
## El niño: la mirada limpia

Giosuè, el niño, lo interpreta **Giorgio Cantarini**, con cinco años cuando se rodó. Lo que hace Benigni con él es **una de las direcciones de actor infantil más sutiles que he visto**. El niño nunca actúa "asustado". Nunca actúa "trágico". Mira las cosas con **la curiosidad limpia** que tienen los niños de cinco años: pregunta, mira, hace caso, juega, llora cuando se aburre. El espectador, **que sí sabe lo que está pasando**, ve las escenas a través de los ojos del niño y a la vez con su propia conciencia adulta. Esa **doble mirada** es lo que carga las escenas de peso.
Hay un momento, hacia el medio de la segunda mitad, en el que el niño dice **"yo no quiero jugar más, quiero irme con mamá"**. Y Guido tiene que convencerle, allí, en el barracón, de que sigan jugando. Le dice que vale, que se vayan, pero que **están a punto de ganar el tanque**. Y el niño, después de mirar, decide quedarse. **Esa decisión, tomada por un niño de cinco años en pantalla, es uno de los momentos más demoledores de la película**. Y lo es **sin que nadie llore, sin música épica, sin diálogos largos**. Es solo la decisión, y la cara del padre que lo ve.
Cantarini, en la siguiente escena, sale al patio del campo y ve un tanque americano que está liberando el campo. Es un momento de júbilo absoluto: el juego ha terminado, han ganado, **el premio es real**. La sonrisa del niño, en esa escena, es **una de las imágenes más bonitas que se han filmado**. Te haces mil preguntas sobre qué significa esa sonrisa, qué le ha quedado al niño de esos meses, cómo va a procesar todo esto cuando crezca. Pero, en el plano, **es solo una sonrisa**, y eso es lo que Benigni quería: que termináramos viéndola.
## Benigni: el director que sabía dónde poner los frenos
Lo que más se le puede reprochar a Benigni como director es que **se le va la mano en algunas escenas cómicas de la primera mitad**. Algún gag es repetitivo, alguna casualidad demasiado forzada, algún ritmo demasiado vodevilesco. Pero, en la segunda mitad, **donde podría haberse desbocado en sentimentalismo o en denuncia sobreexplicativa**, Benigni hace exactamente lo contrario: **frena, contiene, sustrae**.
La escena más comentada de toda la película es la del altavoz: **Guido coge el altavoz del campo y manda un mensaje en italiano a Dora, sabiendo que ella está al otro lado del muro**. La intención es **decirle que él y el niño están bien**. Lo hace con una mezcla de **comicidad y desesperación**. Le pasa el altavoz al niño para que diga **"buongiorno, principessa"**, la frase que solo Dora y Guido reconocen como suya. Es el momento de máxima audacia formal de la película: **un personaje haciendo un chiste íntimo en mitad de un campo de exterminio para que su mujer lo oiga**. Y funciona. Funciona porque **Benigni no carga la escena con música épica**, porque la rueda con cámara cercana, porque los gestos del actor son contenidos, porque dura **lo justo**.
Otra escena: cuando Guido, al final, descubre que ha sido detenido por unos soldados y le van a llevar a un sitio para ejecutarle, pasa por delante del barril donde está escondido su hijo. **Sabe que el niño está mirando**. Y, en lugar de llorar, de gritar, de explicarle al niño lo que pasa, **hace un paso ridículo, marcial, parodia de los soldados que lo escoltan**. El niño se ríe. Y Guido se va a morir. Esa escena se ha discutido durante décadas: ¿es manipulación sentimental? ¿Es la mejor escena de la película? Para mí, **es la mejor escena del cine sobre el Holocausto, y posiblemente del cine sobre paternidad**.
Lo que hace Guido en ese momento no es una performance teatral. Es **un acto último de cuidado**. Sabiendo que el niño le está mirando, **sabe que si llora o grita el niño puede salir del barril y morir también**. Su muerte tiene que parecer **parte del juego**. Y para que parezca parte del juego, **tiene que hacerla cómica**. Esa decisión, tomada por un padre en el último minuto de su vida, **es lo que la película te deja como herencia**: la idea de que **el último acto de un padre puede ser un chiste, si el chiste es lo que mejor protege al hijo**.
## La carta abierta a los hijos
He pensado a menudo, especialmente desde que tengo tres hijas, **qué tipo de carta te deja una película como esta a un padre que la ve**. La respuesta no es académica: es muy concreta.
*La vida es bella* le dice al padre que **el papel de un padre no es protegerlo todo, ni tener todas las respuestas, ni resolver todos los problemas**. El papel del padre es **traducir el mundo al lenguaje del hijo**, en cada momento, lo mejor que pueda. Si el mundo es alegre, lo cuenta alegre. Si el mundo es atroz, lo cuenta a medias, lo cuenta con palabras adecuadas, lo cuenta como un juego si hace falta, **pero no le pasa al hijo más realidad cruda de la que el hijo puede sostener**.
Eso no es mentir. Eso es **traducir**. Y los padres traducen el mundo a los hijos **todo el rato**, sin darse cuenta. Cuando explicas un divorcio. Cuando explicas la muerte del abuelo. Cuando explicas que en la pantalla del teléfono hay gente diciendo barbaridades. Cuando explicas por qué no podéis comprar el último iPhone. Cuando explicas que el amigo de clase no es trigo limpio. **El padre es un traductor**. Y traducir bien es **un oficio**.
Guido es **el traductor extremo**. En condiciones extremas, hace una traducción extrema. Y ahí, en la respuesta a la pregunta "*¿cuánto puede traducir un padre?*", la película le dice al espectador: **mucho más de lo que crees**.
Es **una de las defensas más hermosas de la paternidad** que ha hecho el cine. Y es la razón principal por la que me llega tan hondo.
## Las críticas a la película
Por honestidad: no todo el mundo quiere a esta película. Las críticas más serias son tres y conviene tomárselas en serio.
**Crítica uno: trivializa el Holocausto al envolverlo en comedia.** Quien diga esto **no ha visto la película**, o la ha visto mal. La película no trivializa: **el campo se rueda con dureza visible**, hay selecciones, hay duchas que no son duchas (la escena del médico alemán y la nube de humo), hay enfermos abandonados. Lo que Benigni hace es **dejar el horror en segundo plano**, dejarlo entrar por los bordes, mientras la cámara sigue al padre y al hijo. Eso **no es trivializar; es elegir el foco**. El horror sigue ahí, sigue siendo el horror, pero **no es lo que se cuenta**.
**Crítica dos: idealiza la figura del padre judío que protege a su hijo con ingenio, lo cual es una fantasía consoladora.** Esta crítica tiene **un punto de verdad**. La película no representa la experiencia mayoritaria: la mayoría de los padres judíos llevados a Auschwitz con sus hijos pequeños murieron junto con ellos en cámaras de gas en las primeras horas. La película muestra **un caso excepcional** y lo eleva. Hay que reconocerlo. Pero, dicho eso, **el cine puede contar excepciones**. Y, contando una excepción, puede llegar a verdades más generales. Que esta historia concreta sea improbable no la convierte en falsa: la convierte en **simbólica**. Y la película lo asume desde la primera escena, con la frase del narrador adulto en off: **"esta es una historia simple, pero no es fácil de contar"**. Esa frase es la honestidad de Benigni con el espectador.
**Crítica tres: el personaje cómico de Guido, en las circunstancias extremas, resulta inverosímil; un hombre en un campo de exterminio no se comportaría así.** Esta crítica **olvida lo que Primo Levi y otros supervivientes contaron**: que la respuesta humana al campo era heterogénea, y que algunos prisioneros, especialmente los que tenían responsabilidades sobre otros (padres, hermanos mayores), desarrollaban estrategias de supervivencia emocional **muy parecidas a las de Guido**. No idénticas, claro, pero del mismo género. Hay literatura testimonial que respalda **la verosimilitud psicológica** del personaje, aunque no la verosimilitud literal de los hechos concretos.
Mi posición, en suma, es que las críticas merecen escucharse pero no derriban la obra. La obra **se sostiene** en sus propios términos.
## La música de Piovani
La banda sonora es de **Nicola Piovani**, un compositor italiano que había trabajado con Fellini en sus últimas películas. Piovani ganó el Oscar por esta. La pieza principal, el tema central, es **una melodía circular, ligera, casi de carrusel**, que cuando aparece en la primera mitad funciona como acompañamiento alegre, y cuando reaparece en la segunda **se convierte en una de las músicas más rompedoras del cine**.
Es el mismo principio que usaba Morricone en Cinema Paradiso: **una melodía simple, repetida en distintos momentos, que va cargando significado**. Cuando llega al final, la misma melodía que oíste a los diez minutos te golpea con todo lo que ha pasado en medio. Eso es **un truco antiguo y siempre funciona**.
## Una palabra sobre el Oscar y el famoso salto sobre la butaca
*La vida es bella* ganó **tres Oscar** en 1999: mejor película de habla no inglesa, mejor actor (Benigni) y mejor banda sonora. Y muchos recuerdan, sobre todo, **el famoso momento en que Benigni, al ser nombrado mejor actor, salió saltando sobre los respaldos de las butacas de la audiencia para llegar al escenario**. Esa imagen, repetida en mil parodias, es **lo más Benigni que hay**: un hombre incapaz de contener la alegría, que la hace cuerpo, que la hace gag visual.
Para mí, esa escena del Oscar **explica al director**. Es la misma persona que ha rodado la película. La misma alegría desbordada, la misma incapacidad de contener la energía, la misma fe en que **mostrar emoción ridícula es más honesto que disimularla**. Benigni es así. Y, a quien le moleste, no le va a gustar la película. A quien le encante esa energía, **la película le va a llegar al fondo**.
## A quién se la recomiendo
Se la recomiendo a:
- **Cualquier padre**. Va a aprender cosas.
- **Cualquier persona que crea que la imaginación es una frivolidad menor**. Va a salir convencida de lo contrario.
- **Quien necesite recordar que la dignidad humana puede sobrevivir a casi todo**. Aviso: la película no es optimista barata; es **realista en sus límites** y, dentro de esos límites, esperanzada.
- **Quien ya haya visto otras películas sobre el Holocausto** (*La lista de Schindler*, *El pianista*, *Shoah*, *Hijo de Saúl*) y quiera **otro registro**. *La vida es bella* es un registro distinto, no sustitutivo. Vela después, no antes.
No se la recomiendo a:
- **Quien crea, como cuestión de principio, que el Holocausto no se debe abordar desde la comedia**. Tiene su razón filosófica y la respeto, pero entonces esta película no le va a entrar.
- **Quien no soporte la energía gestual de Benigni**. Su tipo de comicidad **es muy física, muy expansiva, casi vodevilesca**. Si eso le irrita en la primera media hora, la película no le va a remontar.
## Cómo verla
**Versión original en italiano con subtítulos**. La doblada al español es decente, pero el italiano de Benigni es **uno de los activos de la película**. El timbre, la velocidad, las inflexiones, los regionalismos toscanos. Eso, doblado, se pierde.
**No la veas cansado**. La primera hora es muy luminosa y, si llegas a ella agotado, te pide energía que igual no tienes. Mejor una tarde de sábado descansada.
**Si la vas a ver con un hijo o hija**, calcula la edad. Para mí, **a partir de los diez o doce años está bien**. La segunda mitad puede ser dura, pero la mediación de un padre adulto que ve la película junto al hijo y comenta lo que conviene, hace que sea **una experiencia formativa de las grandes**. Yo la vi por primera vez con la mayor cuando tenía trece años y todavía hablamos de ello.
## Lo que viene en la serie
El próximo es **el día 28**, la número 6. Cambiamos de tono completamente: **americana, de los noventa, con un protagonista que se sienta en un banco y le cuenta su vida a quien quiera escucharle**. Si lo adivinas, la prepara con la caja de bombones lista. Y si no, será una sorpresa, espero, agradable.
Las cinco primeras de la lista están publicadas. Quedan cinco. Si has llegado hasta aquí, probablemente vas a llegar al final. Bienvenido.
Hasta el día 28.
---
# La filosofía fuck you money según Joan Tubau
URL: https://javiervalencia.net/post/fuck-you-money-joan-tubau
Hay un concepto del que Joan Tubau lleva años hablando en su podcast y en `mixx.io` y que se me ha quedado pegado: **fuck you money**. La traducción literal —«dinero para decir que te jodan»— suena más vulgar de lo que el concepto merece. Porque no va de ser rico. Va de poder mirar a la cara a tu jefe, a tu cliente, a tu casero o a la siguiente factura sorpresa, y poder decir tranquilamente «no, esto no me lo trago» sin que se te encoja el estómago.
Este post abre una serie de ocho entradas en las que iré desgranando cada uno de los puntos. Pero antes hay que entender el marco: qué es exactamente el *fuck you money*, qué no es, y por qué Tubau lo viste como una filosofía de vida y no como una estrategia financiera.
## El concepto, sin envoltorio
La idea original viene del mundo anglosajón. Se le atribuye a varios: J.P. Morgan, Humphrey Bogart, John Goodman en una escena famosa de *The Gambler*. Todas las versiones cuentan lo mismo: tener suficiente dinero ahorrado como para no depender de la próxima nómina, y por tanto poder decir «no» cuando alguien te ofrece algo que no quieres aceptar.
No es ser millonario. Es tener **reserva suficiente** para que la vida no te obligue.
Tubau coge esa idea y la lleva al contexto español, donde el coste de vida es más bajo, donde despedir cuesta caro al empresario, y donde la cultura del trabajo todavía arrastra mucho «aguanta y calla». En ese contexto, el *fuck you money* no es un número fijo (no son «un millón de euros», no son «25 veces tus gastos anuales»), es una **relación entre tus reservas y tus gastos**. Si gastas poco y has ahorrado bien, mucho antes de lo que crees tienes margen para decir que no.
## Lo que no es
Antes de meternos en lo que sí es, importa cerrar puertas:
- **No es FIRE puro.** El movimiento *Financial Independence, Retire Early* persigue dejar de trabajar para siempre a los 40. Tubau no predica eso. Su lectura es que el trabajo bien elegido puede ser una fuente de sentido, no solo de ingresos. El *fuck you money* no busca que dejes de trabajar, busca que trabajes **en lo que quieras y con quien quieras**.
- **No es ser tacaño.** La frugalidad de la que habla Tubau es selectiva: gasta sin culpa en lo que de verdad te importa, recorta brutalmente en lo que no. No es comer arroz con tomate siete días a la semana ni ducharte con agua fría para ahorrar gas.
- **No es desconfiar del sistema.** No hay aquí prepperismo, ni cripto-utopismo, ni búnker en el monte. Hay bancos normales, fondos indexados normales, contratos de trabajo normales. La sofisticación está en la disciplina, no en el instrumento.
- **No es una clase social.** Esto es lo más importante. Un autónomo cobrando 30.000 € al año con tasa de ahorro del 40% puede tener más *fuck you money* que un directivo cobrando 120.000 € al año que se gasta hasta el último céntimo.
## Las dos palancas: ingresos y gastos
Toda la filosofía descansa sobre una observación muy simple: la libertad no la dan los ingresos, la da el **gap entre ingresos y gastos**.
Si ganas 5.000 € y gastas 5.000 €, eres tan dependiente de tu trabajo como alguien que gana 2.000 € y gasta 2.000 €. Más, de hecho, porque el primero está acostumbrado a un estilo de vida que el mercado laboral no va a sostener si pierde su puesto.
Si ganas 3.000 € y gastas 1.800 €, has metido 1.200 € al colchón cada mes. En diez años son 144.000 € sin contar rentabilidad. Con un 6% medio de los fondos indexados globales, son **del orden de 195.000 €**. Eso son entre cuatro y ocho años de gastos cubiertos sin trabajar. Eso, en cualquier idioma del mundo, es libertad.
Las dos palancas a tocar son por tanto:
1. **Subir el gap**: ganar más o gastar menos. Ambas valen, pero gastar menos suele ser más controlable.
2. **Hacer trabajar el gap**: meterlo en activos que renten más que la inflación. No bajo el colchón. Tampoco en cripto-casino.
Sobre estas dos palancas se montan los ocho puntos que vienen detrás.
## Los ocho pilares de la serie
Cada uno tendrá su propio post. Los enuncio aquí para que se vea el mapa completo:
1. **Vivir por debajo de tus posibilidades.** El paso uno y el más difícil. La trampa de la inflación de estilo de vida empieza con el primer aumento.
2. **Ahorro agresivo y tasa de ahorro.** Por qué importa más el porcentaje que el valor absoluto, y cómo Tubau defiende tasas del 30-50% como punto de partida realista.
3. **Inversión pasiva en fondos indexados de bajo coste.** Mucho ruido en finanzas, pero la respuesta correcta para el 95% lleva décadas sin cambiar.
4. **Diversificación.** En activos, geográficamente, en divisas. Por qué el sesgo casa-país de los españoles es uno de los errores más caros.
5. **Evitar la inflación de estilo de vida.** El sueldo sube y los gastos suben pegados detrás. El veneno silencioso.
6. **Tiempo > dinero.** Por qué un sueldo alto con horarios infames suele ser peor negocio que uno medio con vida propia.
7. **Optionality.** La libertad de poder decir «que te jodan» no es teórica: es una opción que el dinero compra de forma muy concreta.
8. **Antifragilidad y reservas para los malos años.** Las crisis llegan. Quien tiene colchón sale fortalecido; quien no, sale tocado.
## Por qué me interesa todo esto
A mis casi cincuenta tacos, con tres hijas en casa y veinticinco años en el sector, llevo bastante tiempo pensando que **el mejor seguro profesional es no necesitar el siguiente sueldo**. No para irme. Para poder quedarme **en buenos términos**, eligiendo proyectos en lugar de aceptarlos.
Tubau no inventa nada. Bogle ya hablaba de fondos indexados en los setenta, Mr. Money Mustache en los 2000, *The Millionaire Next Door* en los noventa. Pero su lectura tiene tres cosas que me gustan:
- Está **adaptada al contexto español**: salarios, fiscalidad, vivienda, paro, autónomos. No es traducción del *FIRE blog* americano.
- **No es radical**: no exige que te mudes a un pueblo, no exige ducharte a las cinco de la mañana, no exige seguir un sistema de Excel con doscientas columnas.
- **Pone el énfasis en la libertad, no en el dinero**. El objetivo no es el patrimonio, es la opción.
En los próximos ocho posts de la serie voy a entrar en cada uno de esos pilares con detalle, con datos concretos del contexto español de 2026, y con los matices y reservas que cualquier consejo de finanzas personales necesita.
No es asesoramiento financiero. Es lo que yo he ido entendiendo escuchándolo a él, leyendo sobre el tema y aplicándolo (con bastante mediocridad, todo sea dicho) a mi propia vida.
Empezamos en el próximo post de la serie con el más obvio y a la vez el más difícil: **vivir por debajo de tus posibilidades**.
---
# Claude Code Opus 4.7 (xhigh effort) vs OpenAI GPT-5 Codex: comparativa real
URL: https://javiervalencia.net/post/claude-code-opus-4-7-vs-gpt-5-codex
Llevo varias semanas usando en paralelo **Claude Code con Opus 4.7 en modo xhigh effort** y **GPT-5 Codex** sobre el mismo tipo de tareas: trabajo real, no benchmarks sintéticos. Código Go, despliegues, refactors de Rails legacy, scripts de migración, debugging de SQL pesado. Y al final ya tengo opinión formada, así que toca escribirla.
Este post no es una tabla de SWE-bench más. Es lo que veo cuando los dos modelos compiten por hacerme la vida fácil un martes a las 11 de la noche.
## El contexto del experimento
Antes de entrar en harina, las dos herramientas que comparo no son lo mismo:
- **Claude Code Opus 4.7**: el CLI oficial de Anthropic, ejecutándose sobre el modelo Opus 4.7 con ventana de contexto de **1 millón de tokens**. El modo *xhigh effort* es el nivel máximo de razonamiento extendido; el modelo «piensa» más antes de actuar, gasta más tokens y tarda más, pero acierta más en problemas no triviales.
- **GPT-5 Codex**: la variante de GPT-5 que OpenAI ha afinado específicamente para programación. Se usa desde su CLI propio (`codex`), desde el IDE (Copilot Workspace, Cursor con backend GPT-5 Codex) o desde la API.
Ambos son agentes: leen ficheros, ejecutan comandos, escriben código, leen el output y deciden el siguiente paso. No son simples completadores. Por eso la comparación interesante no es «cuál escribe la función más corta para sumar dos enteros», sino **cuál termina la tarea con menos intervenciones mías**.
## Calidad de código generado
En este apartado los dos están **igualados en código correcto**, pero divergen en estilo.
**Claude Opus 4.7 xhigh effort** tiende a producir código más conservador. Prefiere claridad antes que elegancia, escribe nombres largos y descriptivos, y rara vez se inventa abstracciones que no pidas. Cuando le pides un handler HTTP, sale un handler HTTP. Cuando le pides un test, sale un test sin un framework de mocks que no usabas.
**GPT-5 Codex** es más arriesgado y a veces más brillante. Tiene tendencia a meter «toques» que no le has pedido: cachés que no necesitabas, decorators, una capa de abstracción extra «por si acaso». Cuando aciertan, sus soluciones son más vistosas. Cuando fallan, fallan a lo grande, porque la abstracción de más se convierte en un foco de bugs.
En proyectos pequeños la diferencia es marginal. En proyectos grandes —donde el problema no es escribir, es **no romper lo que ya funciona**— el conservadurismo de Opus 4.7 me ahorra horas.
## Razonamiento sobre código existente
Aquí es donde xhigh effort marca diferencia.
Le pasé a los dos el mismo problema: un bug intermitente en un job de Sidekiq que solo aparecía en producción, con un fragmento de log de 200 líneas y tres ficheros relacionados (~1800 líneas en total).
- **Claude Opus 4.7 xhigh effort** se tomó casi dos minutos pensando, leyó los tres ficheros, identificó que el problema era una *race condition* entre dos workers que tocaban la misma fila sin lock pesimista, y propuso la solución con `with_lock` y un test de regresión que reproducía el caso. Acertó al primer intento.
- **GPT-5 Codex** propuso primero un retry con backoff exponencial (lo que oculta el bug, no lo resuelve), y solo cuando le insistí con «mira si hay concurrencia» llegó a la misma conclusión que Claude. Tres iteraciones.
No es que GPT-5 Codex no pueda razonar así. Es que **xhigh effort** parece estar calibrado por defecto para invertir más esfuerzo en estos análisis, mientras que Codex tiende a saltar antes a la acción.
## Trabajo con contextos grandes
La ventana de 1M de tokens de Opus 4.7 cambia el juego en codebases medianos-grandes. Le he metido **el repositorio entero de un cliente** (unas 600.000 tokens entre código y docs internas) y sigue siendo capaz de localizar dónde está definida una función exportada por un módulo que importa otro módulo. La precisión decae un poco hacia el final del contexto, pero no de forma catastrófica.
GPT-5 Codex tiene ventana más corta (272k tokens en su versión actual, según la documentación pública de OpenAI). Para un microservicio sobra; para un monolito de Rails de diez años, no. Acabas teniendo que «cebarlo» con resúmenes y referencias, y al final el agente que se mueve solo por el código se convierte en un consultor al que tienes que explicarle el repo cada conversación.
**Punto para Opus 4.7**, sin matices.
## Uso de herramientas y bucles agentic
Los dos hacen lo mismo en teoría: leen ficheros, ejecutan comandos, escriben código, observan, deciden. En la práctica:
- **Claude Code** tiene un ciclo más predecible. Lanza tres-cinco herramientas en paralelo cuando son independientes, se para a pensar cuando ve algo raro, y casi nunca entra en bucles infinitos. Si no sabe qué hacer, **lo dice**.
- **GPT-5 Codex** es más rápido en cada paso pero más propenso a obstinarse. Le he visto reescribir el mismo test cinco veces tratando de hacerlo pasar antes de admitir que el problema era el código de producción, no el test.
La cultura de «paro y pregunto» de Claude es una de esas cosas que parecen una limitación hasta que llevas tres horas con un agente y aprecias no haber tenido que deshacer veinte commits.
## Refactors grandes
Tarea real: migrar un controlador de Rails de Active Record clásico a un patrón de service objects, con cambios en seis ficheros, tres tests y una migración.
- **Claude Opus 4.7 xhigh effort**: 14 minutos, 27 herramientas usadas, una intervención mía para confirmar el nombre del namespace del service. Diff limpio. Tests pasaban al primer intento.
- **GPT-5 Codex**: 11 minutos, 35 herramientas usadas, dos intervenciones (una para corregir el nombre de un método inventado, otra para revertir un cambio en un fichero que no debía tocar). Tests pasaban tras una iteración extra.
Aquí Codex va ligeramente más rápido en wall-clock pero **gasta más tokens y requiere más supervisión**. Si estás cobrando por hora a un cliente, igual te da igual. Si estás pagando los tokens de tu bolsillo, no.
## Velocidad, latencia y coste
| Eje | Claude Opus 4.7 xhigh | GPT-5 Codex |
|-----|------------------------|-------------|
| Latencia por turno | Alta (10-40s con thinking) | Media (5-20s) |
| Tokens por tarea | Más (xhigh piensa mucho) | Menos en media |
| Coste API (mismo prompt) | Más caro | Más barato |
| Coste por tarea completada | **Suele empatar** | Suele empatar |
La trampa de las comparativas de precio por millón de tokens es que ignoran las iteraciones. Un modelo más barato que necesita tres intentos sale igual o más caro que uno «caro» que acierta a la primera. En mi muestra real, el coste por tarea completada acaba siendo muy parecido entre los dos.
## Alucinaciones e invenciones
Las dos generaciones actuales (Opus 4.x y GPT-5.x) han reducido drásticamente las alucinaciones respecto a 2024. Pero todavía las hay:
- **Opus 4.7** se inventa firmas de métodos de librerías muy nuevas o muy de nicho. Cuando lo hace, suele ser por una vez y al ejecutar el test reconoce el error y se corrige.
- **GPT-5 Codex** se inventa menos APIs pero más **opciones de CLI**. He visto que pone flags inexistentes a `git`, `psql` y `kubectl` con una seguridad pasmosa, y solo se entera del fallo cuando la ejecución revienta.
En código tirando a estándar (Go stdlib, ActiveRecord, FastAPI, Postgres) los dos están bien. En APIs nuevas o privadas, los dos requieren supervisión.
## Cuándo uso cada uno
Después de varias semanas alternándolos, mi reparto es:
- **Claude Code Opus 4.7 xhigh effort** para todo lo que sea **trabajo serio en un proyecto serio**: refactors grandes, debugging complejo, código que va a producción y va a vivir años, lectura de codebases que no conozco. Es mi caballo de batalla.
- **GPT-5 Codex** para **scripts rápidos**, prototipos desechables, generación masiva de boilerplate, exploraciones donde la velocidad importa más que la exactitud. También cuando el trabajo es muy gráfico o visual y quiero la integración fluida con Cursor.
No es competencia, es complementariedad. Pero si tengo que pagar **una sola** suscripción, hoy es Claude.
## Comparativa con otros proveedores
Para no quedarme solo en la pelea Claude vs OpenAI, aquí va un repaso a lo que ofrecen los demás. Los he probado todos en tareas similares aunque con menos intensidad.
| Modelo | Fuerte en | Débil en | Para mí |
|--------|-----------|----------|---------|
| **Claude Opus 4.7 (Anthropic)** | Razonamiento sobre código, contexto largo, agentic loops, fiabilidad | Coste, latencia con thinking | Mi primera opción para programar serio. |
| **GPT-5 Codex (OpenAI)** | Velocidad, integración con Cursor/Copilot, ecosistema | Contexto más corto, se obstina | Excelente para iterar rápido o trabajar dentro de Cursor. |
| **Gemini 3 Pro (Google)** | Multimodalidad real (imágenes, vídeo, PDFs), contexto enorme (2M), precio | Estilo de código a veces verboso, sigue por detrás en agentic raw | Imbatible para analizar pantallazos, vídeos de bugs o PDFs de specs. |
| **DeepSeek V3.1 / R1** | Calidad/precio brutal, abierto, razonamiento decente | Filtro de contenidos peculiar, soporte oficial limitado | Para mí: backend barato cuando el volumen importa. |
| **Grok 4 (xAI)** | Razonamiento general, búsqueda en tiempo real integrada con X | Coding puro algo por detrás de Claude/GPT-5 | Útil para tareas que mezclan código y datos del mundo real reciente. |
| **Qwen3 Coder (Alibaba)** | Open-weights, fine-tuneable, calidad sorprendente en código | Tooling agentic menos maduro, soporte en CLIs limitado | Si necesitas hostearlo tú o trabajar 100% offline. |
| **Mistral Large 2 / Codestral** | Modelos europeos, soberanía de datos, latencia baja en Europa | Razonamiento por detrás del top 3 | Para empresas en la UE con requisitos de jurisdicción. |
| **Llama 4 (Meta)** | Open weights, ecosistema gigantesco, fine-tuning maduro | El base sigue por detrás de los cerrados; necesita afinado para brillar | Para proyectos donde el control del modelo importa más que el último 10% de calidad. |
El orden de calidad pura en **programación agentic** sigue siendo, en mi experiencia hoy: **Claude Opus 4.7 ≳ GPT-5 Codex > Gemini 3 Pro > Grok 4 ≈ DeepSeek > Qwen3 Coder > Llama 4 > Mistral Large**. Pero esa ranking se mueve cada dos o tres meses, así que tómalo con la fecha del post en la mano.
## Lo que sí está claro
Más allá de qué modelo gana, hay tres cosas que sí me parecen cerradas a estas alturas:
1. **El razonamiento extendido funciona.** Los modos «high» y «xhigh» de Claude, o el equivalente «reasoning effort» en OpenAI, mejoran objetivamente las respuestas en tareas no triviales. No es marketing.
2. **Contexto largo importa más de lo que se dice.** Mucha de la diferencia práctica viene de poder meter el repo entero en una conversación.
3. **El agente vale más que el modelo.** Un modelo medio con un buen runtime de herramientas (lectura de ficheros, ejecución, edición segura) bate a un modelo top con un wrapper malo. La calidad del CLI o IDE pesa tanto como el modelo subyacente.
Mañana sale otro modelo y todo esto envejece. Pero hoy, 25 de mayo de 2026, con un café delante y el editor abierto, **Claude Code Opus 4.7 en xhigh effort es la herramienta que más palanca me da por euro y por hora**. GPT-5 Codex es un segundo cercanísimo y, según la tarea, me convence más. El resto están más lejos, pero ninguno es ya un juguete.
Si me preguntas dentro de seis meses te diré otra cosa. Pero esa es la gracia.
---
# Las diez mejores películas de mi vida: 4. Casablanca
URL: https://javiervalencia.net/post/top-10-peliculas-4-casablanca
Hay películas que envejecen mal. La mayoría de las películas de los años cuarenta envejecen mal. La actuación se nota teatral, el ritmo lento, el blanco y negro disimula peor de lo que parece, los diálogos suenan a otro siglo. Y, sin embargo, **una película de 1942 sigue funcionando con cualquier público, en cualquier sala, en cualquier idioma**. Esa película es *Casablanca*. Y la cuarta de esta serie sobre mis diez películas favoritas es, obviamente, esta.
Antes de seguir: si entras frío, lee primero la [número uno](/post/top-10-peliculas-1-cinema-paradiso). La serie va del puesto uno hacia abajo (sí, al revés que las series de top diez convencionales). Hoy es la cuatro. La razón de elegir este orden, larga, está en el primer post.
## La paradoja de Casablanca

La paradoja central de *Casablanca* es que **es una película hecha en plena Segunda Guerra Mundial, con guion improvisado sobre la marcha, con un director que no tenía especial fama, con un protagonista que solo había hecho secundarios, con un final escrito al borde del rodaje, y que aún así salió perfecta**. Las películas perfectas suelen ser fruto de directores obsesivos con control total de la obra (Kubrick, Hitchcock, Coppola con tiempo y dinero). *Casablanca* es la excepción: salió perfecta **por accidente**. O, mejor dicho, por una combinación de talentos individuales que se alinearon en el momento exacto.
El estudio (Warner Bros.) compró los derechos de una obra de teatro no estrenada (*Everybody Comes to Rick's*) y la metió en producción casi inmediatamente. Curtiz, el director, era un húngaro-judío que llevaba años haciendo películas de género en Hollywood sin demasiado prestigio. Humphrey Bogart era un actor con más años que carrera de protagonista. Ingrid Bergman era una sueca con éxito creciente pero no estelar. El reparto secundario se construyó casi todo con **actores europeos huidos del nazismo** (algunos detalles biográficos de los actores secundarios son escalofriantes: muchos habían perdido familia recientemente). La música la compuso un austriaco. El equipo técnico era una mezcla de inmigrantes recientes y americanos. Y los hermanos Epstein y Howard Koch escribieron el guion **a tres bandas, con cambios diarios**.
Eso, en condiciones normales, da una película mediocre. La excepción a la regla es que **toda esa gente sabía lo que estaba en juego**, no sólo profesionalmente sino moralmente. Las escenas de los refugiados en Casablanca, las escenas del bar de Rick, las escenas del momento en que cantan La Marsellesa por encima del himno alemán... fueron filmadas por gente que **había vivido eso, en otra ciudad, unos años antes**. Y se nota.
## Rick, Ilsa y el sacrificio como argumento
La historia, por si entras de cero, es la siguiente. **Rick Blaine** (Bogart), americano expatriado, regenta un bar/casino llamado Rick's Café Américain en Casablanca, Marruecos, durante la Segunda Guerra Mundial. Casablanca es un punto de tránsito para refugiados europeos que intentan llegar a Lisboa y de ahí a América. Rick es **cínico, distante, no toma partido**. Hasta que una noche entra en su bar una vieja amiga, **Ilsa Lund** (Bergman), acompañada de su marido, **Victor Laszlo**, líder de la resistencia checa. Ilsa y Rick tienen un pasado en París, justo antes de la ocupación nazi de la ciudad, que terminó de forma turbia. Ese pasado, los visados que Rick tiene escondidos, y la necesidad de Laszlo de escapar de Casablanca, son los tres motores de la película.
Lo que se nos cuenta en realidad no es una **historia de espionaje**, aunque la trama de visados sea su excusa. Lo que se nos cuenta es **una historia de sacrificio**. Rick tiene en sus manos la decisión que le permite quedarse con la mujer que ama o dejarla ir con su marido para que ella pueda ayudarle a seguir luchando contra los nazis. La película es **el camino hacia esa decisión** y el peso emocional de tomarla. El final, en la pista de aterrizaje, con la frase de Rick a Ilsa **"si no subes a ese avión, lo lamentarás. Quizá no hoy, quizá no mañana, pero pronto, y durante toda tu vida"**, es uno de los momentos más citados del cine.
Y la pregunta clave que la película plantea, y que poca gente desarrolla: **¿es Rick un héroe o es un cobarde con suerte?**. Rick toma la decisión de dejar ir a Ilsa porque es lo correcto. Pero también porque, **conociéndose como se conoce, sabe que con él no sería feliz**. Y porque, **siendo Rick como es Rick, una vida feliz tampoco le iba a sentar bien**. El sacrificio, en su caso, también es **un acto de autoconocimiento**: sabe que él no es para esto. Que su sitio es estar en el bar, con Sam tocando el piano, con conversaciones a media voz, con whisky. La heroicidad **es también una forma de fidelidad a uno mismo**. Eso es lo que hace que el personaje no sea plano.
## Bogart: el cínico con corazón inventado para siempre

Bogart, antes de *Casablanca*, era un actor que llevaba quince años en Hollywood y casi siempre interpretando **villanos o secundarios duros**. *El halcón maltés*, el año anterior, le había dado el papel de Sam Spade y ya apuntaba que podía sostener un protagónico. Pero fue *Casablanca* lo que lo convirtió en el icono que conocemos: **el cínico con corazón**, el hombre desencantado que sigue creyendo en algo pero que se niega a admitirlo en voz alta.
Lo que hace Bogart con Rick es **construir un personaje a base de silencios y de gestos pequeños**. Rick no monologa sobre sus sentimientos. Rick se sirve un whisky. Rick mira la lluvia. Rick toca con el dedo el borde del vaso. Rick mira a alguien sin pestañear. La interpretación está hecha **de microacciones**, no de grandes momentos. Y, cuando llega un gran momento (la pista de aterrizaje al final), Bogart lo hace **sin alzar la voz**, casi en susurros, lo cual lo carga de mucha más fuerza.
Esto es una lección de actuación que se sigue estudiando: **el contenido emocional se transmite mejor por debajo de la línea**, no por encima. Si Bogart hubiera gritado en la escena final, la película habría caído. Susurra. Y ahí, en el susurro, hay un océano.
Hay además **una cuestión de cara**. Bogart no tenía la cara del galán de la época. Tenía una cara dura, no convencionalmente guapa, con una cicatriz pequeña en el labio que le daba un tic característico. Pero esa cara, **filmada con la luz exacta**, en blanco y negro, en plano cerrado, en silencio, transmite gravedad mejor que cualquier galán uniforme. La generación que descubrió a Bogart en *Casablanca* y luego en *El sueño eterno* (1946) y *Cayo Largo* (1948), reconoció que **podías ser un hombre interesante en cine sin ser bello convencional**. Eso abrió la puerta a actores que vinieron después (Mitchum, Cooper en sus últimos años, eventualmente Newman, hasta llegar a Brando).
## Bergman y los ojos que sostienen el plano
Ingrid Bergman tenía 27 años cuando rodó *Casablanca*. Aún tenía acento sueco notable en inglés (lo conservaría toda la vida). Era una belleza no convencional para Hollywood: alta, de rasgos más fuertes que los de las estrellas femeninas habituales, con cejas marcadas. El estudio había dudado si convenía que se quitara las cejas y se las redibujara, como hacían con las otras actrices. La negativa de Bergman a hacerlo fue **una de las decisiones que la salvaron**: su cara, sin retocar, es lo que la hace memorable.
Lo más impresionante de Bergman en la película es **lo que hace con la mirada**. Hay una escena, en el bar, en la que Ilsa ve a Rick por primera vez después de años. La cámara se acerca lentamente a su cara. No dice nada. **Solo mira**. Y en esa mirada está todo: el reconocimiento, la culpa, la nostalgia, el amor que sigue ahí, el miedo a lo que va a pasar. Esa es **una de las grandes miradas de la historia del cine**, y se la sigue enseñando a estudiantes de actuación como ejemplo de "todo en los ojos".
Ilsa, como personaje, es más complicada de lo que parece. No es la chica buena que sufre. **Tiene su propio conflicto moral**, ella misma; le mintió a Rick en París al irse sin avisar (creía que su marido había muerto, recibió la noticia de que estaba vivo, dudó y eligió). En Casablanca, **vuelve a estar entre dos hombres**: el que ama (Rick) y el que admira (Laszlo). Y la decisión que se le pide tomar no es entre dos sentimientos, sino entre **dos formas de vida**: la del corazón roto pero pegada al amor, o la del corazón cumplido pero abierto al mundo. Ella, al final, **no decide**. Decide Rick por ella. Y ella se sube al avión.
Esa es **una de las críticas modernas a la película** desde una perspectiva feminista: que la mujer al final no toma su propia decisión. Tiene su parte de razón. Pero también hay que entender que **Ilsa, en la escena anterior con Rick en el bar, le ha dicho explícitamente "decide tú por los dos, no puedo más"**. Y Rick decide. Quien interprete eso como debilidad pierde matiz: es **una entrega lúcida de decisión**, no una incapacidad. Y los personajes femeninos del cine de los años cuarenta que toman sus propias decisiones con esa madurez no son legión.
## La estructura: el guion mejor escrito de la historia del cine clásico
Hay quien dice que el guion de *Casablanca* es el mejor guion clásico jamás escrito. Voy con esos. Y la razón es muy concreta: **cada línea de diálogo hace dos cosas a la vez**. Avanza la trama y construye personaje. Avanza la trama y dice algo sobre el mundo. Avanza la trama y plantea un tema moral. **No hay relleno**.
El bar de Rick es el escenario principal: una especie de Babel europea en miniatura. Refugiados de toda Europa intentando salir, oficiales nazis intentando controlar, oficiales franceses intentando sobrevivir entre las dos cosas, jugadores de ruleta, prostitutas, traficantes. Cada escena en el bar **incluye al menos tres pequeños arcos paralelos** que están pasando en distintas mesas mientras la trama principal avanza. Eso da la sensación de **un microcosmos en funcionamiento**, no de un escenario teatral.
Los diálogos están cargados de **frases que se han convertido en patrimonio cultural**. La lista, sin exagerar:
- "Of all the gin joints in all the towns in all the world, she walks into mine." ("De todos los bares de todas las ciudades del mundo, tenía que entrar en el mío.")
- "Here's looking at you, kid." ("A tu salud, muñeca." — la traducción canónica al español, aunque imperfecta.)
- "We'll always have Paris." ("Siempre nos quedará París.")
- "I think this is the beginning of a beautiful friendship." ("Creo que este es el comienzo de una bonita amistad.")
- "Round up the usual suspects." ("Detengan a los sospechosos habituales.")
- "Play it, Sam. Play 'As Time Goes By'." ("Tócala, Sam. Tócala otra vez.") — Nota: la famosa frase "tócala otra vez, Sam" no aparece exactamente así en la película. Es una de las citas mal recordadas más famosas del cine.
Una sola película no debería contener este nivel de frases míticas. *Casablanca* las contiene porque **el guion era extraordinariamente concentrado**. No había espacio para flojera: cada minuto tenía que justificar su existencia. Y el resultado es que casi cada minuto **se ha quedado en la memoria colectiva**.
## La Marsellesa contra el Wacht am Rhein

Si pudiera elegir una escena entre todas, sería esta. Voy a explicarla porque es **una de las más emocionantes que el cine americano ha producido jamás**.
En el bar de Rick, una noche, unos oficiales alemanes empiezan a cantar el *Die Wacht am Rhein* (himno militar alemán) acompañados al piano. Su tono es triunfal, casi provocador, dentro de un local lleno de refugiados europeos. Laszlo, el marido de Ilsa, baja al piso de abajo, se acerca a la orquesta del bar y le dice al director: **"toquen La Marsellesa"**. El director duda, mira a Rick, que está al fondo. Rick le hace una señal con la cabeza: **adelante**.
Y entonces empieza La Marsellesa. La orquesta toca. Y, uno a uno, **todos los clientes del bar se levantan** y empiezan a cantar. La cámara va recorriendo las caras: refugiados, jugadores, viejos, jóvenes, mujeres, hombres. **Todos cantando con los ojos llorosos**. La voz se va imponiendo sobre la del piano alemán, que se va apagando. Los oficiales nazis terminan callando.
Lo que hace que esta escena sea diferente a cualquier escena de cantos patrióticos en cine es **que la mayoría de los actores que cantan La Marsellesa eran refugiados reales del nazismo, exiliados en Hollywood**. Algunos lloran de verdad en pantalla. **No están actuando**. Y se nota. Esa autenticidad la convierte en una escena que va más allá de la ficción: es **una pequeña ceremonia colectiva** filmada en plena guerra por gente que estaba viviéndola.
Cuando la veo, todavía hoy, ochenta y tres años después, **se me hace un nudo en el pecho**. No me pasa con muchas escenas de cine.
## La música: "As Time Goes By" y el papel de Sam
La canción central de la película, *As Time Goes By*, ya existía: era de 1931, compuesta por Herman Hupfeld para un musical de Broadway. El guionista Howard Koch quiso meterla porque la había oído años antes y le había gustado. Max Steiner, el compositor que hizo la banda sonora orquestal, odió la canción y trató de cambiarla por una composición suya original. **Pero no se pudo**, por razones logísticas (Ingrid Bergman ya había rodado las escenas en las que pide oírla, y le habían cortado el pelo para la siguiente película; no se podía repetir el rodaje).
Esa imposición accidental fue **una de las mejores cosas que le pasaron a la película**. *As Time Goes By* es una canción **simple, con letra que parece banal pero que en realidad habla exactamente del tema de la película**: "*the fundamental things apply, as time goes by*". Es decir: el tiempo pasa, pero las cosas esenciales (un beso es un beso, un suspiro es un suspiro, el amor es amor) **siguen siendo lo que son**. Esa es la idea central de Casablanca: **el tiempo y la guerra no cambian lo que importa**.
El otro gran momento musical es **Sam**, el pianista negro del bar, interpretado por Dooley Wilson. Sam es uno de los pocos personajes negros de un protagonista en una película de Hollywood de 1942 que **no es un estereotipo**. Es el confidente de Rick, su amigo, la voz de la razón. Cuando Ilsa le pide tocar *As Time Goes By*, Sam se resiste porque sabe que esa canción **va a dolerle a Rick**. Lo toca porque ella se lo pide, pero claramente preferiría no hacerlo. Esa pequeña fidelidad de Sam a su amigo Rick, sin decirla, es **uno de los detalles de personaje más bonitos de la película**.
## Lo personal: por qué Casablanca llega a un español de cuarenta y muchos
Para mí, *Casablanca* tiene dos puntos de entrada personal. El primero, más obvio, es **la cuestión del sacrificio amoroso**. Casi nadie que haya pasado de los cuarenta no ha tenido que tomar una decisión parecida a la de Rick en pequeño formato: dejar ir a alguien (o algo) porque era lo correcto, aunque fuera lo que más quisiera. Eso, en un mundo donde el discurso dominante es **"si quieres algo, lucha por ello"**, es contracultural. *Casablanca* defiende, sin pestañear, **que a veces lo correcto es soltar**. Que querer no es un derecho. Que algunos amores no se pueden mantener sin destrozar otras cosas más importantes. Esa lección, en una película de 1942, es **más madura que casi todo lo que se rueda hoy**.
El segundo punto, menos obvio, es **el papel del bar como espacio social**. El Rick's Café Américain es un sitio donde **gente de muchas procedencias se cruza, conversa, hace negocios, llora, se enamora, traiciona, se reconcilia**. Es un sitio donde **hay vida**. Y los bares así, en mi experiencia, **han desaparecido**. Los bares de hoy son sitios para comer rápido, ver el fútbol o tomar la cerveza de salida del trabajo y volver a casa. El bar de Rick es otra cosa: es **una pequeña plaza pública privada**. Es lo que el barrio londinense llama *pub*, lo que la peña flamenca de Las Lagunas (de la que [hablo en otra serie](/post/pena-flamenca-el-gallo-mijas-presentacion-de-la-serie)) sigue siendo a pesar de todo, lo que era una casa de comidas española en los años setenta. Esa **socialidad densa** de la película es una de las cosas que más añoro al verla.
Y, claro, está la **estética**. La luz, el blanco y negro, la lluvia en el cristal, los sombreros, las gabardinas, los pitillos, las miradas a media luz. Hay una belleza visual en *Casablanca* que **se ha convertido en gramática del cine clásico**. Cualquiera que haya hecho una película de bar, con luz de neón, con humo y con misterio, ha imitado a *Casablanca* aunque no lo sepa.
## Lo que no funciona (porque algo siempre no funciona)
Por honestidad: no todo en *Casablanca* es perfecto. La primera media hora **se nota más teatral** que el resto. Algunos secundarios sobreactúan a la manera de la época. La música de Steiner, en algunas escenas, **sobrecarga** lo que ya se sostiene visualmente. Hay un par de subtramas (la pareja búlgara, por ejemplo) que se sienten más esquemáticas. Y la traducción al español canónica, "Tócala otra vez, Sam", es **incorrecta** y ha hecho un flaco favor a la película durante décadas.
Pero estos detalles son **rasguños en una obra mayor**. La película, en su conjunto, **sobrevive a sus defectos**. Y eso, con películas de más de ochenta años, es lo más que se puede pedir.
## ¿Por qué la cuatro y no más arriba?
He intentado responderme a esta pregunta y la respuesta más honesta es: **porque Casablanca, aunque la admiro casi sin reservas, no me toca personalmente como sí lo hacen Cinema Paradiso, Cadena perpetua y El padrino**. Es una película que admiro como **obra mayor**, no tanto como **experiencia íntima**. Probablemente por la distancia temporal: yo no viví los años cuarenta, ni la Segunda Guerra Mundial, ni la Casablanca colonial francesa. La película es brillante pero **no es mía**. Es de mi padre, en cierto modo, y por extensión mía por herencia cultural.
A diferencia de las tres anteriores, donde puedo decir "*esta es mi vida en pantalla*", aquí solo puedo decir "*esta es una de las películas más perfectas que he visto*". Y eso, en términos de top diez personal, **vale puesto cuarto, no podio**.
## A quién se la recomiendo
Se la recomiendo a:
- Quien diga "no veo películas en blanco y negro". A esa persona, por encima del resto. *Casablanca* es la película de iniciación al blanco y negro: en cuanto la veas, dejas de pensar que el blanco y negro es algo viejo y empiezas a verlo como **un estilo**.
- Quien esté en un momento de **decisión sentimental difícil**. La película no te da una solución, pero te muestra que ha habido otros antes que tú.
- Quien quiera entender por qué **el cine clásico de Hollywood sigue importando**. *Casablanca* es la respuesta corta a esa pregunta.
- Quien guste de los **diálogos brillantes**. Hay cinco frases para el resto de la vida en esta peli.
No se la recomiendo a:
- Quien necesite actualidad visual. La película, aunque envejece bien para la edad que tiene, sigue siendo **una película de 1942**: planos contados, escenarios pequeños, montaje contenido.
- Quien quiera **acción**. Aquí no hay tiroteos largos, no hay persecuciones, no hay nada de lo que el cine actual entiende por movimiento.
## Cómo verla
**No la veas doblada, si puedes**. La doblada al español está bien, pero la voz original de Bogart es **uno de los activos de la película**. La gravilla en la voz, el cansancio en el tono, la pausa antes de cada frase. Eso, doblado, se pierde.
**Subtítulos en español, voz original**. Es la combinación canónica.
**Tarde de domingo lluviosa, café con leche, mantita, móvil fuera**. Es una película para esa configuración exacta.
**Y, si te sobra tiempo, encadénala con *Tener y no tener* (1944)**. Misma pareja de actores (Bogart y Bacall, en este caso, no Bergman), mismo tipo de ambiente, distinta historia, también notable. Bogart y Bacall se conocieron en ese rodaje, se enamoraron, se casaron, y la química está en pantalla. Te queda una doble sesión memorable.
## Lo que viene en la serie
Mañana, **el día 26**, la número 5. Vuelve la cuota italiana, pero no es Tornatore. Hablamos de **una película que pasa, en parte, dentro de un campo de concentración**, y que, a pesar de eso, es una de las películas más esperanzadas y luminosas que se han filmado. Si no sabes a cuál me refiero, te aviso de que es de los noventa y la protagoniza un cómico convertido en director y actor. Si lo adivinas, ten preparado un pañuelo para el día 26.
Hasta entonces.
---
# Pisto de boletus y huevo campero en El Gallo: cocina actual escondida en una pizarra de peña
URL: https://javiervalencia.net/post/pena-flamenca-el-gallo-pisto-de-boletus-y-huevo-campero
Séptima entrega de la serie sobre la cocina de Paco Flores en la peña flamenca El Gallo, en Las Lagunas de Mijas. Después de los [bastones de berenjena con miel de caña](/post/pena-flamenca-el-gallo-bastones-de-berenjena-con-miel-de-cana), saltamos al plato más **inesperado** de la pizarra. El que más me sorprendió la primera vez que lo vi escrito en la lista. El que confirma que esta cocina **lee** y no solo cocina: **pisto de boletus y huevo campero**.
Este es el plato que mejor explica, por sí solo, por qué la cocina de Paco no encaja en el cliché de "cocina de peña flamenca = cocina tradicional pesada". Encaja en la pizarra y se hace en barra, sí, pero el plato pertenece a la cocina contemporánea española. Es de los que se piden en restaurantes con pretensión, en vajilla cara, con mantel blanco. Aquí lo sirven en mesa de barra o en terraza, sin más ceremonia, y eso es exactamente lo que lo hace memorable.
## Qué es el plato
El pisto, en cocina española, es la guarnición o plato principal a base de **verduras estofadas en aceite**: pimiento, cebolla, calabacín, tomate. Lento, paciente, casero. Cuando funciona, es uno de los platos vegetales más reconocibles del país.
La versión que sirve Paco **sustituye las verduras por boletus**. Los boletus (probablemente boletus edulis, según temporada y disponibilidad) se trabajan en sartén con la misma técnica del pisto: aceite, fuego medio, paciencia hasta que la seta suelta el agua y la concentra de nuevo en sabor. La textura final es **densa, jugosa, con dosis de umami brutales** que las verduras del pisto clásico nunca alcanzan. Encima, **un huevo campero cuajado**, con la yema todavía líquida o semi-líquida, listo para romperse en el primer cucharazo y mezclarse con el boletus.
El plato se sirve en cazuela pequeña o en plato hondo. Se come caliente, con cuchara, y con pan al lado para mojar al final.
## El boletus: producto que pide respeto
Los boletus son **producto serio**. No son las setas culturales del invernadero (champiñón, portobello, pleurotus), que son perfectamente válidas pero no juegan en esta liga. Los boletus son setas silvestres recolectadas en bosque, con temporadas concretas (otoño largo, primavera corta) y precio acorde.
La cocina de boletus tiene tres reglas básicas:
1. **Limpieza, no lavado.** Los boletus no se lavan en agua porque la absorben como una esponja y pierden textura. Se limpian con cepillo y trapo seco, retirando la tierra del pie. Si el sitio los lava, se nota: textura blanda, pérdida de aroma.
2. **Corte adecuado.** En láminas gruesas o en cuartos, según tamaño. Cortes finos pierden cuerpo en sartén; cortes excesivos no se cocinan bien por dentro.
3. **Sartén caliente, aceite limpio, paciencia.** Las setas, al contacto con la sartén caliente, sueltan agua. Esa agua tiene que **evaporarse** para que la seta se concentre. Si se cocina con prisas, queda hervida en su propio jugo y pierde la mitad del juego.
Cuando el boletus se trabaja bien, **se vuelve carnoso, denso, con un sabor profundo a tierra y bosque** que es uno de los más característicos de la cocina española de temporada. La versión de Paco tiene exactamente esa densidad: el boletus es protagonista, no relleno.
## El huevo campero: por qué importa
Aquí hay otro punto que mucha gente ignora. **No todos los huevos son iguales.** Los códigos en la cáscara (0-, 1-, 2-, 3-) marcan el tipo de cría: ecológico, campero, suelo, jaula. Hay diferencias claras entre todos ellos:
- **Huevo de jaula** (3): industrial, gallinas en jaulas. Cáscara fina, yema pálida, sabor lineal. La opción más barata.
- **Huevo de suelo** (2): gallinas sueltas en nave, sin acceso a exterior. Algo mejor que el anterior.
- **Huevo campero** (1): gallinas con acceso a exterior, alimentación más variada. **Yema más naranja, más densa, sabor más rico.**
- **Huevo ecológico** (0): cría ecológica certificada. Calidad consistente, normalmente la mejor disponible en mercado.
El plato de Paco lleva **huevo campero**, no de jaula. **Esto es perfectamente perceptible al ojo y al paladar**: la yema, al romperla, baja por el boletus en color naranja intenso, no amarillo pálido; al probarla, sabe a yema, no a "huevo industrial".
La diferencia entre cocinar este plato con huevo de jaula y con huevo campero es enorme. La yema es **el agente ligante** de toda la cazuela: cuando se rompe, emulsiona con el aceite del pisto y crea una textura cremosa que envuelve cada bocado de boletus. Si la yema es pálida y plana, esa emulsión no funciona como debe. Es un detalle aparentemente menor pero **central**.
## La técnica del cuajado
El huevo, en este plato, **no se fríe**. No es huevo a la plancha ni huevo escalfado. Se **cuaja encima del pisto**, en la cazuela, en horno a fuego medio o tapando la sartén unos minutos.
Cuajar significa:
- **La clara cuaja**: pierde transparencia, queda firme.
- **La yema queda líquida o semi-líquida**: caliente pero sin cocinarse del todo. El centro tiene que romperse al pinchar y bajar.
El punto del huevo es **el examen del cuajado**. Demasiado tiempo y la yema se cocina, queda dura, el plato pierde toda la idea. Demasiado poco y la clara está babosa, mal hecha. El equilibrio es exacto: clara firme, yema cremosa.
En El Gallo lo bordan. La yema, intacta hasta el primer pinchazo, baja por el boletus tibio y crea esa emulsión casi salsa. El plato, en ese momento, **se transforma**: pasa de ser pisto de boletus con huevo encima a ser un guiso unificado, untado, redondo.
## Los detalles que cuentan
Lo que distingue la versión de Paco de versiones correctas pero no memorables:
- **Fondo del pisto bien reducido.** El boletus suelta agua; esa agua debe evaporarse hasta dejar una textura jugosa pero no caldosa. Si el plato llega con líquido en exceso en el fondo, algo va mal.
- **Aceite de oliva virgen extra serio.** Este plato lo va a marcar. Aceite picante o intenso (picual) puede tapar; aceite suave (arbequina) acompaña mejor.
- **Sal en escama al final.** Sobre la yema cuajada, antes de salir a mesa. Aporta el toque que cierra el plato.
- **Pan al lado, sin pedir.** En El Gallo lo traen. La cazuela termina mojada con pan.
## Acompañamientos: ninguno
Este plato es **autocontenido**. No pide guarnición, no pide segundo plato vegetal, no pide mucho más en la mesa. En una mesa de cuatro, el pisto de boletus y huevo campero ocupa el lugar del **plato sorpresa** o del plato vegetal serio, y se sirve casi como entrante elaborado.
Lo que **funcionaría junto** en una mesa más amplia:
- Antes: alcachofa con brandada (mismo registro vegetal serio).
- Después: secreto ibérico o solomillo (carne contundente que cierra mesa).
Lo que **no encaja al lado**: otra cazuela igualmente untuosa (huevos rotos, callos), otra fritura (los buñuelos rompen la línea).
## Maridaje: tres opciones
Este plato pide **vino con cuerpo y limpieza**, capaz de sostener el umami del boletus sin pelear con la yema cremosa.
1. **Blanco con cuerpo, fermentado en barrica corta.** Un blanco gallego con barrica (Albariño con paso de barrica), un godello del Bierzo, un blanco de Rueda de bodega seria. La barrica corta complementa el boletus sin apropiarse del plato.
2. **Tinto joven sin barrica.** Garnacha de Aragón joven, mencía de Bierzo. Frutosos, frescos, con acidez que limpia paladar. Este tinto sí es bienvenido (a diferencia de la alcachofa con brandada o los buñuelos), porque el boletus pide cuerpo en copa.
3. **Pinot Noir** (en versión asequible, no Borgoña de las caras). Si la peña tiene la oportunidad de ofrecerlo, el Pinot Noir es el maridaje canónico de las setas en cocina internacional. Su elegancia y su acidez encajan perfectamente.
**Lo que evitaría**: blancos jóvenes muy aromáticos (sauvignon blanc en exceso de tiol), tintos con barrica larga (la barrica tapa la seta), espumosos secos (no encajan con la yema).
## Cómo pedirlo
1. **Pídelo de plato sorpresa**, no de tapa estándar. Es de los platos que conviene anunciar a la mesa: "vamos a probar el pisto de boletus", para que se preste atención.
2. **Cazuelita para uno o dos, no para mesa entera.** Es plato de paladar fino, no de banquete. Compartirlo entre dos lo aprecia mejor que entre cuatro.
3. **Caliente, sin esperar.** Como toda cazuela con yema cuajada: el momento del primer pinchazo es el momento. No esperes a que llegue todo lo demás a la mesa.
## Para terminar
El pisto de boletus y huevo campero es, para mí, **la sorpresa de la pizarra de El Gallo**. Es el plato que demuestra que el chef no se conforma con la cocina de peña tradicional, aunque la respeta y la ejecuta con honestidad. Es **cocina actual** en el sentido bueno: aprende, lee, traduce, y sirve un plato que estaría perfectamente en pie en un restaurante con manteles, vajilla cara y precio doble. Aquí cuesta lo que cuesta una tapa o una ración en una peña flamenca, y se come en la barra de abajo o en la terraza.
Esa **incoherencia entre el formato del local y la ambición del plato** es exactamente lo que hace especial este sitio. Y este plato lo concentra mejor que ningún otro.
La próxima entrega cierra la serie: **corazón de atún encebollado**. El despiece menor del atún (aleta amarilla), por qué hay que aprender a pedirlo en zonas como esta, y qué dice de una cocina que un chef ponga en pizarra cortes que casi nadie pide.
---
# Las diez mejores películas de mi vida: 3. El padrino I y II
URL: https://javiervalencia.net/post/top-10-peliculas-3-el-padrino
A día de hoy, *El padrino* y *El padrino: parte II* son, para mí, **la misma película**. Una de unas seis horas y media, cortada en dos por razones de logística cinematográfica. Las he visto siempre como un único proyecto narrativo. Las he visto al menos seis veces, casi siempre seguidas, casi siempre con un par de pausas para café. La he recomendado siempre como pack. Y, sí, por eso ocupan **un solo puesto** en este top diez: el número tres. Si tuviera que ponerlas por separado, las dos estarían entre los cinco primeros, lo que tampoco sería justo para las otras de la lista.
Esta es la tercera entrega de la serie. Si entras frío, conviene leer primero la [número uno](/post/top-10-peliculas-1-cinema-paradiso). La serie va de la uno hacia abajo, no al revés. La regla que acabo de romper hoy es la de "una película por entrega". El espíritu, no. Cualquiera que haya visto la I y la II me da la razón sin que tenga que insistir mucho.
## Por qué la I y la II son una sola película

Coppola dirigió las dos. Coppola escribió las dos con Mario Puzo. Las dos se estrenaron con dos años de diferencia (1972 y 1974). Y, más importante, **las dos cuentan la misma historia con un solo arco**: el de la transformación de Michael Corleone, hijo no destinado al negocio familiar, en el padrino más despiadado y, paradójicamente, más solo de la trilogía. La parte I lo coloca dentro del negocio; la parte II lo coloca dentro de sí mismo. Sin la I no hay II. Sin la II, la I está incompleta.
Pero hay algo más. La parte II hace algo que casi ninguna secuela en la historia del cine ha hecho: **expande la I hacia atrás**, contando la historia de cómo el padre, Vito Corleone, se convierte en padrino. Así, mientras Michael en el presente se hunde más en el frío, su padre, Vito, en el pasado, está construyendo lo que Michael acabará por heredar. El montaje paralelo entre las dos líneas temporales, separadas por casi cuarenta años, no es un truco: es el corazón estructural de la película. Lo que la convierte en una **obra completa**, no en una saga.
Cuando alguien me dice que ha visto *El padrino* pero no la II, le digo que no la ha visto. Que ha visto **media película**. Por eso no separo: porque no se pueden separar.
## El padrino I: la sangre fría que se hereda
La parte I es, en su superficie, **la novela de un cambio de generación**. Vito Corleone, padrino veterano, sufre un atentado. Sonny, el hijo mayor, asume el mando provisional. Fredo, el segundo, no sirve. Michael, el más joven, está fuera del negocio y eso le honraba. La película es **el camino por el cual Michael acaba dentro**, no a regañadientes sino con una **frialdad que sorprende a su propia familia**. La famosa escena del restaurante (Michael mata al policía y al rival de la familia) es el punto de no retorno: ahí deja de ser Michael Corleone, el chico que iba al frente para servir al ejército americano, y empieza a ser Michael Corleone, el padrino futuro.
Pacino, en su primera gran encarnación cinematográfica, hace algo que se sigue estudiando: **construye la frialdad por capas**. En las primeras secuencias es un chico tímido, casi blando, novio de Kay Adams, un poco fuera del clan. Cuando recibe la información de que su padre ha sufrido un atentado y se decide a actuar, hay una pausa muy larga. Y a partir de ahí, los gestos de Pacino se empiezan a contener. Habla más bajo. Mira más fijo. Sonríe menos. Es como si el actor estuviera **borrando** al personaje original, sin decirlo, durante dos horas y cuarenta. La transformación es invisible y total.
La parte I tiene, además, **la mejor secuencia de bautismo de la historia del cine**. La del bautismo del sobrino de Michael, en la iglesia, intercalada con asesinatos múltiples ordenados por él en paralelo. El cura pregunta "*¿renuncias a Satán?*" mientras matan a varios rivales en distintos sitios de Nueva York. Es una secuencia que **definió el montaje paralelo moderno**. Y es, posiblemente, **la metáfora más cínica que el cine americano se ha permitido jamás**: la mafia rezando.
## El padrino II: el espejo
La parte II es **el peor lugar** al que puede ir un hijo después de haber heredado lo que tiene Michael. Y la película es exactamente eso: el descenso. Michael no se da cuenta de que está hundiéndose porque en cada momento concreto **sus decisiones son lógicas**. Quitar a Hyman Roth, lógico. Sospechar de Fredo, lógico. Acabar con Fredo, lógico. Distanciarse de Kay, lógico. Cada paso, mirado en su contexto, está justificado. Pero la suma de los pasos lleva a Michael a un sitio que el primer Michael, el del restaurante, nunca habría aceptado. Y la película te hace **ver el camino entero como espectador externo**, mientras él, dentro, sigue creyendo que está actuando con cabeza.
Lo más cruel de la parte II es la última escena. Michael solo en el jardín, sentado, mirando al vacío, recordando una cena familiar de antes del atentado en la que él era el único que decía "yo no quiero entrar en el negocio". Sus hermanos vivos, su padre vivo, su familia entera alrededor. Y él, ahí, recordando ese momento desde **el sitio más solo del mundo**. Esa última escena es **la frase final de la película**, sin que nadie diga una palabra. Lo dice todo.
Aparte de la línea de Michael, en la parte II tenemos **la mejor interpretación de Robert De Niro hasta la fecha** y posiblemente de su carrera: el joven Vito Corleone. De Niro hereda el papel de Marlon Brando de la parte I y, en lugar de imitarlo, **lo reconstruye desde cero**. El joven Vito que llega a Nueva York en 1901, mudo, asustado, hambriento, y que veinticinco años después se ha convertido en el padre de la familia que Brando interpreta. De Niro, que apenas habla inglés en la película (casi todo es siciliano subtitulado), gana **un Oscar por una actuación en otro idioma**, lo cual es un récord raro.
## Por qué El padrino III no entra
Es justo que aclare esto porque mucha gente me lo pregunta. *El padrino: parte III* (1990) no entra en mi lista no porque sea mala sino porque **es claramente inferior a las dos primeras**. Tiene problemas estructurales: la trama del Vaticano es confusa, el personaje de Sofia Coppola (sustituyendo a Winona Ryder en el último momento por enfermedad) no está al nivel, la dirección de actores está más floja, y la película pierde el tono operístico que tenían las dos primeras. No es una **catástrofe**, como la pintan algunos. Pero no está a la altura.
Lo importante es entender que las dos primeras son **una historia cerrada**. La parte II termina con Michael solo y la línea narrativa **ha terminado**. Lo que cuenta la III es un epílogo dieciséis años después, con la familia muy cambiada y con Coppola muy cambiado también. El epílogo está bien para fans, pero no añade nada esencial. La obra real es las dos primeras.
Para mí, *El padrino* es **el ejemplo máximo de cómo dos películas pueden ser una sola y una tercera puede quedar fuera del canon de la propia obra**. No es la única en la historia del cine: pasa también con *El señor de los anillos* (las tres son la obra), con *Star Wars* original (las tres también). En *El padrino* lo curioso es que la I y la II son inseparables, y la III es separable. Eso ya dice cosas.
## La música de Nino Rota

La banda sonora de *El padrino* es de **Nino Rota**, el compositor italiano que también había trabajado con Fellini. La pieza principal, la *Speak Softly Love* (o tema principal), es de las cinco melodías más reconocibles del cine. Te toco cuatro notas en una trompeta y a cualquier hijo de vecino le cae automáticamente la imagen de Brando con un gato en el regazo.
Lo que hace que la música funcione tan bien aquí es que **es siciliana de raíz**. Coppola podría haber elegido una banda sonora americana, orquestal, hollywoodiense. Pero quería que la película sonara desde dentro, **desde la Sicilia que estos personajes llevan dentro a pesar de vivir en Nueva York**. Y Rota le dio exactamente eso: una música de mandolina y trompetas, melancólica, lenta, con un toque fúnebre que se infiltra incluso en los momentos de celebración (la boda al principio, el bautismo al final).
La parte II añade composiciones nuevas de Carmine Coppola (padre de Francis), incluyendo el tema del joven Vito que se mezcla con el tema principal. Y aquí, otra vez, el detalle italiano: cuando Vito vuelve a Sicilia ya hombre adulto, una de las piezas más sobrecogedoras suena, y la imagen no requiere palabras. Es **música como narración**.
Yo, cada vez que oigo *Speak Softly Love*, vuelvo automáticamente al jardín en el que Brando juega con el nieto al principio de la I y le da una rodaja de naranja. Esa secuencia es **una de las más tiernas que ha filmado el cine criminal**. Que un personaje al que hemos visto ordenar venganzas durante dos horas termine jugando con un nieto como cualquier abuelo del mundo es, en sí, el resumen de toda la obra: estos son **familias antes que mafiosos**. El problema, claro, es que también son mafiosos.
## La fotografía: el aceite oscuro de Gordon Willis
Una palabra sobre el aspecto visual, porque también es parte de por qué estas películas son lo que son. El director de fotografía es **Gordon Willis**, apodado en su época "el príncipe de las tinieblas" precisamente por su trabajo en *El padrino*. La película está **muy poco iluminada**, deliberadamente. Hay escenas, sobre todo las del interior del despacho de Vito en la I, en las que casi no se ven los ojos de los personajes. Eso era escandaloso en 1972: el director de fotografía pensaba que **el espectador no necesitaba ver los ojos** del padrino. Que el padrino habitaba la sombra y desde la sombra se hablaba con él.
Esa decisión visual (que la productora odiaba y casi le obliga a corregir antes de estrenar) acabó siendo **una de las decisiones formativas del cine de los setenta**. La iluminación baja, el contraluz, la cara medio en oscuridad como expresión de carácter. Se sigue copiando hoy.
En la parte II, la fotografía hace algo aún más sofisticado: **distingue las dos líneas temporales por color**. Las secuencias del joven Vito tienen un **sepia** cálido, casi como una fotografía antigua revelada con tinta. Las secuencias de Michael en el presente tienen un **azul frío**, casi gélido. Sin que el espectador tenga que mirar fechas en pantalla, **sabe en qué tiempo está** por la temperatura del color. Eso es maestría.
## Brando y la masticación del algodón
Imposible hablar de la parte I sin hablar de **Marlon Brando** como Don Vito Corleone. Es la actuación que define al personaje y, en cierto modo, al icono. Brando, en 1972, era un actor de los grandes pero **estaba en una época bajita de su carrera**. Aceptó el papel cobrando relativamente poco, hizo una audición histórica en la que pidió pañuelos para meterse en la boca (de ahí la voz pastosa y los carrillos hinchados del padrino que todos imitamos) y ganó el Oscar a mejor actor (y lo rechazó, enviando a la actriz Sacheen Littlefeather a recogerlo para denunciar el trato a los indios americanos en cine, episodio que merece una entrada propia).
Lo brillante de Brando en *El padrino* es que **no construye un personaje impactante por el grito, sino por la pausa**. Don Vito habla bajo. Habla despacio. Acaricia a su gato. Sonríe poco. No alza la voz casi nunca. Cuando dice "voy a hacerle una oferta que no podrá rechazar" lo dice como quien comenta el tiempo. Y, sin embargo, todo el universo de la película **gira alrededor de su autoridad invisible**. Es la mejor lección posible sobre cómo se construye un jefe en cine: **no por lo que hace, sino por cómo le miran los demás**.
Esa actuación influyó en todos los jefes del cine que vinieron después. En *Los Soprano*, James Gandolfini construyó a Tony con conciencia de Don Vito. En *Breaking Bad*, Walter White en sus últimos episodios mira a Pacino-como-Michael. Y por supuesto en innumerables imitaciones, parodias y referencias. Don Vito es el icono universal del padrino. Y lo es porque Brando se atrevió a hacerlo callado.
## La mafia que no glorifica la mafia
Hay un debate viejo sobre si *El padrino* glorifica el crimen organizado. Mi posición es **que no, en absoluto**, y que quien lo crea no ha visto la película hasta el final. Las dos partes terminan con Michael Corleone **destrozado emocionalmente**, solo, sin familia, sin amigos, sin la mujer que quería, sin paz. La película no nos vende **que la mafia compense**. Nos vende que la mafia **te come desde dentro** aunque ganes todas las batallas.
Lo que pasa es que **el camino hacia esa destrucción está filmado con una elegancia operística** que puede confundir. Las escenas de violencia están coreografiadas como pequeñas óperas: las pistolas, las luces, la música, los planos. Y eso, en una primera lectura, puede parecer admiración. Pero la admiración formal es por **el cine**, no por la moral. La moral de la película es claramente crítica. **Michael, al final, ha ganado todo y ha perdido todo**. Eso no es una invitación a ser Michael. Es una advertencia.
Para entender bien esto basta con compararlo con *Goodfellas* de Scorsese, una década después. Scorsese filma la mafia desde dentro, desde el subidón, desde la energía. *El padrino* la filma desde fuera, desde la pena, desde el ritmo lento. Las dos son grandes películas. Pero la mirada moral es muy distinta. *Goodfellas* te coloca dentro del coche cuando va rápido. *El padrino* te coloca en el funeral cuando termina.
## Lo personal: por qué me llega esta película
Llevo mucho tiempo pensando por qué *El padrino* me llega de la manera en que me llega. Tiene poco que ver con la mafia, evidentemente, y mucho con dos ideas que me obsesionan: **la herencia familiar** y **el peso de las decisiones tomadas sin caer en cuenta**.
La idea de la herencia familiar (la herencia entendida como el conjunto de las cosas que uno hereda **sin pedirlas**: el carácter del padre, el oficio del padre, la red de relaciones del padre, la moral del padre) está en el centro de la película. Michael nunca pidió ser padrino. Le tocó porque su hermano mayor era inviable, porque su otro hermano no servía, porque su padre fue atacado, porque el destino. Eso, llevado al terreno ordinario, le pasa a mucha gente: uno hereda **una versión de sí mismo** que no eligió. El trabajo del padre. La vergüenza del padre. La capacidad económica del padre, o la falta de ella. El barrio donde creció el padre.
La pregunta de Michael (que la película no responde, deliberadamente) es: **¿hasta qué punto soy yo y hasta qué punto soy mi familia?**. Es una pregunta que a los cuarenta y muchos uno entiende mejor que a los treinta. Y *El padrino* la plantea con un nivel de precisión que pocas otras películas igualan.
La segunda idea (las decisiones tomadas sin caer en cuenta) es más concreta y se resume en una frase: **uno no decide grandes cosas en grandes momentos; uno decide grandes cosas en momentos pequeños sin saber que está decidiendo grandes cosas**. Michael no decide ser mafioso. Decide vengar a su padre, lo cual le parece razonable. Luego decide protegerse, lo cual también le parece razonable. Luego decide proteger a su familia, etc. Cada paso es razonable. La suma es Michael.
Eso, sin que tengamos vidas de mafiosos, le pasa a todo el mundo. Uno se acostumbra a su trabajo sin haber decidido conscientemente que iba a estar veinte años en él. Uno se queda en una pareja sin haber elegido conscientemente quedarse. Uno se muda al barrio en el que se queda treinta años. La vida se hace **por decisiones pequeñas que parecen lógicas**, no por grandes deliberaciones. Y a veces, a los cincuenta, uno se sienta en el jardín y mira para atrás como Michael en la última escena.
## Cómo verlas
Mi recomendación, después de varios intentos a lo largo de los años, es esta:
**Vélas juntas, en orden de estreno, con una pausa en medio.** No la versión "cronológica" (que combina las dos en orden temporal del joven Vito, llamada *The Godfather Saga*): ese montaje destruye la magia del montaje paralelo. Mira la I un día, descansa una noche, mira la II al día siguiente. Total: seis horas y media, distribuidas en dos sesiones.
**No la veas con prisa.** Esta no es una película para ver en plataformas mientras haces otra cosa. Pide tu atención completa. Si no se la das, te vas a perder los planos largos, las miradas, las pausas, los detalles de fotografía. Es una película de **detalles cumulativos**, no de impacto inmediato.
**Si nunca la has visto, ten paciencia con la I.** La primera media hora de la I es un montaje de boda muy lento, con personajes que se van presentando uno a uno. La gente joven hoy lo encuentra **lento**. Y lo es, deliberadamente. Pero cada minuto está armando el resto. Una vez termina la boda, la película no se para hasta el bautismo final dos horas y pico después.
**Léete después algún artículo sobre cómo se rodó.** Hay anécdotas memorables: que el estudio quería echar a Coppola varias veces, que Pacino tampoco gustaba al principio, que el caballo de la cabeza fue real (de un matadero), que el famoso "I believe in America" de la primera escena fue idea de Coppola en el último momento, etc. Saber el contexto te hace apreciarla todavía más en la segunda visita.
## Lo que tienen en común con las dos anteriores
Si estás siguiendo la serie, ya hemos visto Cinema Paradiso (la 1) y *Cadena perpetua* (la 2). Las tres son, en el fondo, **películas sobre el paso del tiempo**. Las tres cubren décadas. Las tres tienen un personaje joven y un personaje viejo, y la dialéctica entre los dos sostiene la película. Las tres tienen bandas sonoras inmortales. Las tres están narradas, en parte, **desde la mirada hacia atrás**: Salvatore que recuerda, Red que narra, Michael que mira al jardín al final de la II.
Hay un detalle más que las tres comparten: ninguna de las tres es **americana al cien por cien**. Cinema Paradiso es italiana. *Cadena perpetua* es americana pero filmada con una sensibilidad casi europea de tiempo lento. Y *El padrino* es la película americana más italiana de la historia: dirigida por un italoamericano (Coppola), con música de italiano (Rota), con un protagonista que va a Sicilia, con diálogos en siciliano subtitulados, con un casting profundamente italoamericano. **El cine americano de los setenta**, lo mejor de él, estaba lleno de directores europeos o de inmigrantes recientes. Coppola, Scorsese, De Palma, Cimino, todos italianos. Y eso se nota.
## Lo que viene en los próximos posts
El próximo, **el día 25 de mayo, la número 4**. Otra película clásica, blanco y negro, romance imposible, sacrificio, una de las más citadas de la historia del cine. Aquí siempre nos vamos a entender.
Si esta serie te está enganchando, te aviso de que se pone más emocional en las siguientes. *El padrino* es la más "épica" de las diez. A partir de la 4 entramos en aguas más sentimentales, donde la lágrima asoma con más facilidad. Si has llegado hasta aquí, los próximos posts también van a funcionarte.
Hasta el día 25.
---
# Bastones de berenjena con miel de caña en El Gallo: el plato gancho que no es solo gancho
URL: https://javiervalencia.net/post/pena-flamenca-el-gallo-bastones-de-berenjena-con-miel-de-cana
Sexta entrega de la serie sobre la cocina de Paco Flores en la peña flamenca El Gallo, en Las Lagunas de Mijas. Después de los [buñuelos de bacalao](/post/pena-flamenca-el-gallo-bunuelos-de-bacalao), seguimos en territorio de fritura andaluza, pero cambiando completamente de producto y de juego: **bastones de berenjena con miel de caña**.
Es uno de esos platos que, en cualquier carta de Andalucía, aparece marcado en negrita como "para compartir", "tapa típica" o "imprescindible". En la mayoría de los sitios, sin embargo, es un plato **gancho**: barato de hacer, fácil de servir, vendido a sobreprecio porque "es de aquí". Y, en demasiadas ocasiones, bastante mediocre. Berenjena aceitosa, miel demasiado dulce, sal escasa o nula, plato pesado y olvidable.
Por eso escribir sobre los bastones de berenjena es escribir sobre un plato que muchas cocinas tratan con desidia. Y, por eso mismo, vale la pena escribir sobre la versión que sirve Paco, que es de las que recuerdan que **este plato gancho aparente esconde un examen técnico tan serio como cualquier otro**.
## El plato y su historia
Antes de entrar en técnica, una nota de origen. La berenjena frita con miel es un plato de **herencia andalusí** clarísima. La berenjena llegó al sur de Iberia con la cultura árabe, y la combinación con sirope dulce (originalmente arrope, más tarde miel de caña) es una receta que aparece en recetarios desde el siglo XIII. Es uno de los platos de la cocina española con genealogía más larga, y la razón por la que sigue en pizarra ocho siglos después es que **funciona**: el contraste de la berenjena ligeramente amarga con el dulzor de la miel es uno de los maridajes vegetales más antiguos del Mediterráneo.
Las versiones modernas usan **miel de caña** (melaza derivada de la caña de azúcar, característica de la zona de Frigiliana en la Axarquía), pero hay variantes con miel de abeja, con arrope (mosto reducido) o con sirope de palma. Cada una cambia el plato.
## Qué pide la berenjena
La berenjena es un vegetal **traicionero**. Tiene mucha agua, esponja como una bayeta, absorbe aceite con una facilidad que asusta. Una berenjena mal trabajada se chupa el aceite de la sartén, queda blanda por dentro y aceitosa por fuera, y aparece en plato como una masa parda triste.
Tres pasos pre-fritura son críticos:
1. **Corte uniforme.** Bastones de un dedo de grueso, largos pero manejables. Si los cortas demasiado finos, se queman; demasiado gruesos, no se hacen por dentro. Un dedo es la medida.
2. **Sal previa.** Espolvorear sal sobre los bastones cortados y dejar **20-30 minutos**. La sal extrae líquido (lo verás como gotas en el plato) y reduce la cantidad de aceite que la berenjena absorberá después. Este paso es **opcional según escuela** pero, en mi experiencia, marca diferencia clara.
3. **Secado.** Antes de llevar al aceite, **secar los bastones con papel** o paño. La humedad superficial provoca salpicaduras, baja la temperatura del aceite y empeora la fritura.
Algunos chefs dan también un paso de **harina ligera** antes de freír (harina especial fritura o harina de tempura). En la versión de Paco hay un rebozado fino, casi invisible al ojo, que aporta una capa crujiente sin pesar.
## La fritura
La fritura de la berenjena pide **aceite limpio, abundante y a temperatura justa** (175-180 °C, como en los buñuelos). Aquí los principios son los mismos:
- **Aceite limpio**: si está usado y tiene partículas en suspensión, los bastones absorberán el sabor.
- **Aceite abundante**: la berenjena tiene que poder moverse en el aceite sin amontonarse. Echar muchos bastones de golpe baja la temperatura, los bastones absorben aceite, sale plato grasiento.
- **Temperatura justa**: en bastones, dos a tres minutos suelen ser suficientes. Más, y se queman; menos, y quedan crudos.
Una vez fritos, los bastones se sacan a **rejilla** (no a papel absorbente, que los humedece desde abajo) durante unos segundos, **se sazonan con sal en escama** y se sirven inmediatamente. **Cualquier minuto de espera entre la fritura y el plato pierde textura.**
## La miel: tres opciones, una correcta
Aquí está el segundo examen del plato. ¿Qué miel?
- **Miel de caña.** La miel de caña es un sirope espeso, oscuro, casi negro, derivado de la caña de azúcar. Es **distinto** del azúcar moreno y **distinto** del sirope de arce. En la Axarquía malagueña, en Frigiliana, hay un molino tradicional que sigue elaborándola. Sabor: notas tostadas, ligeramente amargas en el final, dulzor profundo no plano. Es la **opción canónica** para este plato y la que mejor casa con la berenjena.
- **Miel de abeja.** Más floral, más ligera, más dulce de manera frontal. No casa tan bien con la berenjena porque su dulzor es demasiado lineal. Hace el plato más empalagoso. Sirve si no hay otra cosa, pero es una concesión.
- **Sirope de caña** (alternativa industrial a la miel de caña). Más barato, más uniforme. **No es lo mismo.** Sabor más plano, sin las notas tostadas, dulzor frontal. La diferencia es perceptible.
En El Gallo se sirve con **miel de caña genuina**, no sirope. Esto se nota al primer bocado: la miel deja un final ligeramente amargo, complejo, que abre el plato en lugar de cerrarlo.
## La proporción: ni mucha ni poca
Otro punto donde hay margen para fallar: **cuánta miel echar**.
- **Demasiada miel**: el plato se vuelve postre. La berenjena queda nadando en sirope, el contraste se pierde, dos bocados y empalaga.
- **Demasiado poca**: el plato se queda en berenjena frita salada, sin la dimensión que justifica el plato.
La proporción correcta es **un hilo fino sobre los bastones**, suficiente para que cada bocado tenga un toque dulce sin que el dulce domine. En El Gallo, la miel viene en hilo lateral o en mancha controlada, no en charco. Eso es respeto al plato.
## Acompañamientos y presentación
El plato, bien hecho, **no necesita acompañamiento**. Es un plato cerrado: bastón de berenjena crujiente, miel de caña, sal en escama, punto y final.
Lo que **rompe el plato**:
- Limón. Algunos sitios lo añaden por costumbre. La acidez no aporta y mata el matiz amargo de la miel de caña.
- Salsas (mayonesa, alioli). Aplastan el contraste.
- Polvo de queso o decoraciones modernas. No es ese plato.
## Maridaje: tres opciones
Los bastones de berenjena con miel de caña aceptan más opciones de bebida que los buñuelos, porque el plato tiene una capa dulce que se puede acompañar de varias maneras. Tres opciones:
1. **Vermouth artesano de la zona.** Vermouth rojo de hierbas, frío, con hielo o sin hielo. La amargura del vermouth contrasta con la miel y limpia paladar entre bocados. Es probablemente el mejor maridaje para este plato.
2. **Tinto joven afrutado**. Una garnacha o un tinto joven de Sierras de Málaga, sin barrica, fresco. La fruta del tinto resuena con la miel sin sumar dulzor, y la acidez limpia la fritura. Muy buena opción para mesa de cuatro.
3. **Blanco aromático seco** (verdejo de Rueda, moscatel seco de Málaga). Frescura para acompañar el plato sin pelear con la miel.
**Lo que evitaría**: vinos dulces (suman al dulzor del plato y empalagan), espumosos brut nature (la sequedad pelea con la miel), cervezas IPA (lúpulo agresivo aplasta la berenjena).
## Cómo pedirlos
1. **En tapa al inicio o en ración para compartir en cualquier momento.** Es plato versátil. En tapa, sirve perfectamente como aperitivo de barra; en ración, como plato vegetal de mesa.
2. **Comerlos calientes.** No esperar. La textura crujiente se pierde en cinco minutos.
3. **Pedir miel de caña, si en la pizarra hay duda**. Algunos sitios ofrecen "berenjena con miel" sin especificar. Vale la pena preguntar. Si te dicen que es miel de abeja, plantea pedirla aparte para echar tú mismo en cantidad controlada.
## Para terminar
Los bastones de berenjena con miel de caña son uno de esos platos que **discriminan cocinas honestas de cocinas perezosas**. Casi cualquier sitio los tiene en pizarra; pocos los hacen como deben. La diferencia es perfectamente perceptible para cualquier comensal medio. En El Gallo, los hacen como deben: corte, sal previa, fritura limpia, miel de caña genuina, sal en escama al final, plato servido caliente.
Es uno de esos platos donde, después del primer bocado, te das cuenta de que **la cocina del sur sigue viva en los detalles**. Ocho siglos de receta acumulada, ejecutados sin alharacas en una pizarra de peña, en una zona donde casi todo el mundo se conforma con la versión rápida.
La próxima entrega de la serie: **pisto de boletus y huevo campero**. Cocina actual escondida en una pizarra de peña, y por qué este plato es la sorpresa más grande de la carta.
---
# Las diez mejores películas de mi vida: 2. Cadena perpetua
URL: https://javiervalencia.net/post/top-10-peliculas-2-cadena-perpetua
Si Cinema Paradiso fue la película que me cambió a los veintitantos, *Cadena perpetua* fue la que me ayudó a aguantar a los treinta y muchos. Cuento por qué.
La descubrí tarde, lo cual ya dice cosas. Se estrenó en 1994, hizo una taquilla discreta, salió con poco ruido, y fue el alquiler de VHS y el boca a boca lo que la convirtió en lo que es hoy: la película mejor valorada de IMDB, la primera que aparece cuando preguntas a un grupo de hombres de cuarenta años "¿cuál es la mejor peli que has visto?". A mí me la recomendó, exactamente como tenía que pasar, **un compañero de trabajo en una cena de empresa** después de varias copas, mientras me contaba que estaba pasando una mala época y que esa película le había sostenido. La vi al fin de semana siguiente. Tuvo razón.
Antes de seguir, contexto rápido por si entras frío a la serie. Esta es la segunda entrega de un top diez de mis películas favoritas. La serie va **del puesto uno al diez**, no al revés. Es decir, la número uno la solté el [16 de mayo](/post/top-10-peliculas-1-cinema-paradiso) y a partir de ahí voy bajando. Esto que estás leyendo es la número dos. La razón de hacerlo así, larga de explicar, está en el primer post.
## La película más empática jamás filmada

Si tuviera que reducir *Cadena perpetua* a una palabra, esa palabra sería **empatía**. No me refiero a la empatía como discurso, ni como bandera, ni como emoticono. Me refiero a la empatía operativa: la habilidad para ponerte en la piel de otra persona durante dos horas y sentir lo que esa persona siente. Pocas películas la consiguen tan bien. Cuando termina, sales de la sala (o del salón) con la sensación de haber sido amigo de Andy Dufresne durante diecinueve años. No de haberlo "visto" diecinueve años en pantalla. De haberlo **acompañado**. Y eso es muy difícil de hacer en cine.
La película es, en su superficie, una película carcelaria. Andy Dufresne, banquero de profesión, es condenado por el asesinato de su mujer y su amante, crimen que no ha cometido. Entra en la prisión de Shawshank en los años cuarenta y allí pasa décadas. Su narrador y mejor amigo es Red, otro preso, un hombre que también lleva décadas dentro y que se ha convertido en un veterano del sistema. Lo que sigue es una crónica detallada de la vida en la prisión, las relaciones que se construyen entre presos, las que se construyen con guardias y dirección, los pequeños horrores cotidianos y las pequeñas victorias. Y, por encima de todo, la convicción de Andy de que **la esperanza es un ejercicio diario**, no un sentimiento. Y de Red de que **la esperanza es peligrosa**, hasta que deja de serlo.
Lo brillante de la película no es la trama. Es **el ritmo al que avanza el tiempo**. Las dos horas y veinte que dura la película cubren casi dos décadas. Y, sin embargo, no se sienten apresuradas. Lo que hace Darabont (y los cortes de montaje, soberbios) es que tú, espectador, **percibas el paso del tiempo a la misma velocidad a la que lo perciben los presos**. Eso no se aprende en escuelas de cine. Eso se hace bien una vez en la carrera.
## Andy Dufresne y la fortaleza tranquila

De los muchos personajes memorables del cine, Andy Dufresne ocupa un lugar especial en una categoría que llamaría **fortaleza tranquila**. Andy no es un héroe convencional. No es violento. No es carismático. No tiene el don de la palabra. Es un hombre delgado, de movimientos pausados, que habla poco y siempre a media voz, que parece debil físicamente y que aún así nunca acaba de quebrarse.
¿Cómo se construye un personaje así? Tim Robbins, en una de las grandes interpretaciones de los noventa que no ganó Oscar, lo construye **por sustracción**. No usa los recursos típicos del actor de Hollywood: nada de gritos, nada de monólogos, nada de gestos grandes. Lo que usa son **silencios largos**, **miradas que sostienen** y un acento de Nueva Inglaterra que casi no levanta la voz. La diferencia entre el Andy del primer día (asustado, lloroso, perdido) y el Andy de los años intermedios (sereno, irónico, con un punto de superioridad invisible) está hecha sin que casi notes los pasos. Como cuando un hijo crece y un día te das cuenta de que ha crecido.
Lo que tiene Andy, y lo que hace que se quede dentro del espectador, es que **es un personaje que se aferra a una cosa concreta** durante diecinueve años: a la idea de que él, allí dentro, sigue siendo él. La cárcel intenta convertirlo, como convierte a todos los presos, en un miembro más de un sistema que se autoperpetúa. Andy se niega. No de forma estridente: simplemente se niega. Pone discos clásicos en los altavoces de la prisión un día. Aprende a hacer cerveza para los compañeros que están reformando un tejado. Insiste durante años en que se monte una biblioteca decente. Hace cosas que **un hombre libre haría**, dentro de un sitio donde nadie se ve como hombre libre.
## Red y la fragilidad de los fuertes

Si Andy es la cabeza de la película, Red es su corazón. Y esa es, posiblemente, **la mejor decisión que toma Darabont**: que el narrador no sea el protagonista. Que veamos a Andy desde fuera, a través de los ojos de su amigo, no desde dentro de su propia cabeza. Eso protege al personaje de Andy de la tentación del monólogo interior y de la sobreexplicación. Lo deja **opaco**, en el buen sentido: nunca sabemos del todo qué piensa Andy, lo que añade misterio.
Red, interpretado por Morgan Freeman en lo que muchos consideran su mejor papel (y eso es decir mucho), es **el hombre que ha aprendido a sobrevivir en Shawshank renunciando a sus propias expectativas**. Es lo que él mismo llama "estar institucionalizado". Cuando Brooks, el bibliotecario viejo, sale en libertad condicional después de cincuenta años dentro y no aguanta el mundo de fuera, Red lo entiende a fondo. Sabe que él mismo está en ese camino. Y la película es, en buena parte, la historia de cómo **Andy lo va sacando lentamente de ese camino sin que él se entere**.
Hay algo muy honesto en cómo retrata Darabont la amistad masculina entre estos dos. No hay grandes declaraciones. No hay ese momento típico de cine en el que uno mira al otro y le dice "te quiero, hermano". Hay, en su lugar, **gestos pequeños que se acumulan durante años**: el ajedrez que Andy talla, las cartas que Red consigue de fuera, la vergüenza compartida cuando un guardia llega y los interrumpe, las cervezas en el tejado, los discos de ópera. Y un pacto, hecho casi de pasada, de que **si alguno sale, el otro lo va a buscar a un sitio concreto**.
Esa amistad, contada así, sin sentimentalismos, es **lo más cerca que ha estado el cine de retratar bien lo que pasa entre dos hombres adultos que se quieren**. No tienen que decírselo. No tienen que demostrarlo en una escena climática. Lo viven, y se nota. Yo eso lo veo y pienso en mis amigos de toda la vida (esos a los que ya he dedicado [un post entero](/post/amigos-de-toda-la-vida)) y se me hace un nudo. Porque así es. Así es la amistad de adultos: no se anuncia, se mantiene.
## Cómo se cuenta la esperanza sin caer en la cursilería
Una de las trampas más fáciles del cine es contar historias de esperanza haciéndolo desde la cursilería. La esperanza es un sentimiento difícil de retratar bien porque enseguida se vuelve **discurso motivacional**. *Cadena perpetua* es la película que mejor he visto evitando esa trampa. Y lo hace con un truco simple: **trata la esperanza como una práctica, no como una emoción**.
Andy no tiene "esperanza" en el sentido en que la tiene un cartel de oficina. Andy tiene un proyecto. Tiene tareas concretas. Tiene cosas que hacer mañana, dentro de tres meses, dentro de tres años. La esperanza, en su versión, **es una agenda**. Tiene cosas en la lista. Las va tachando. Eso es lo que la hace creíble.
Una de las frases más famosas de la película (de las muchas que tiene memorables) es la que le dice Andy a Red, hablando del Pacífico: "*Hope is a good thing, maybe the best of things*. *And no good thing ever dies*". Y a continuación añade lo que importa: que la esperanza **se cultiva**, no se siente. La esperanza es el agua que vas echando todos los días a una planta que no sabes si va a florecer.
Esto, que en cualquier otra película sonaría a anuncio de seguros, en *Cadena perpetua* funciona porque **la película te ha enseñado durante dos horas que Andy hace eso, no lo dice**. Cuando llega la frase, ya no es discurso: es resumen. Y eso es muy difícil de conseguir.
## La banda sonora invisible
Voy a meterme con un detalle que casi nadie destaca y que para mí es decisivo: la banda sonora de Thomas Newman para *Cadena perpetua* es **una de las mejores y más invisibles** que se han compuesto. Newman es el compositor que más ha entendido que la música, en cine, no tiene que tirar de ti hacia adelante; tiene que dejarte estar donde estás.
Su tema principal, *End Titles*, es una pieza minimalista, con un piano muy poco tocado, una cuerda que entra discreta y un crescendo que no es un crescendo (es más bien una **respiración**). No te dice "emocionate". Te invita a respirar. Y, si eso pasa con la imagen correcta delante (Red caminando hacia la playa, Andy esperándolo bajo el árbol), entonces, sí, te emocionas.
Aprendí mucho de Newman viendo cómo trabajaba esta película y luego *American Beauty*. Es un compositor que practica la **virtud rara** de no robarle protagonismo al actor. Donde Williams o Zimmer empujan, Newman acompaña. Y esta película es, posiblemente, su mejor ejemplo.
Hay otro detalle musical que merece la pena. La escena de las arias italianas, cuando Andy logra colarse en el despacho del director y poner Mozart en los altavoces de la prisión. Es una secuencia que se construye sobre una pieza de una ópera, *Le nozze di Figaro*, cantada por dos sopranos. Y la escena no tiene apenas diálogo. Es la cara de Andy, las caras de los presos parados en el patio, las caras de los guardias sin saber qué hacer, y la voz de las dos sopranos volando por encima del muro. La narración de Red, encima, dice algo así como **"no tengo ni idea de qué cantaban esas dos italianas. Y prefiero no saberlo"**. Y luego: durante esos dos minutos, todo Shawshank fue libre.
Esa secuencia, sola, justifica la película. Pero la película no se acaba ahí.
## Lo que esta película tiene en común con Cinema Paradiso
Si miras *Cinema Paradiso* y *Cadena perpetua* en el mismo año, te das cuenta de que **comparten más de lo que parece**. Las dos son sobre **maestros y discípulos**. Alfredo y Salvatore en una; en *Cadena perpetua* la relación maestro-discípulo es bidireccional: Andy le enseña a Red a tener esperanza, y Red le enseña a Andy cómo se sobrevive en Shawshank. Las dos son sobre **el paso del tiempo** como protagonista invisible. Las dos están narradas desde **una mirada hacia atrás**: en *Cinema Paradiso* es el adulto Salvatore quien recuerda; en *Cadena perpetua* es el viejo Red quien narra. Las dos terminan con una **playa que representa libertad** (Mojácar, simbólicamente, en la mente de Salvatore al volver a Sicilia; Zihuatanejo, literalmente, para Red).
Y las dos comparten algo todavía más profundo: las dos están hechas con **paciencia**. No son películas de ritmo rápido. Te piden que te quedes con ellas. Te recompensan si te quedas. Y eso, en una época en la que las películas se ven con el móvil al lado y se pausan cada quince minutos, es casi un gesto de resistencia.
Si Cinema Paradiso es la número uno y *Cadena perpetua* es la número dos, no es porque haya una grieta entre ellas. Están las dos en la misma altura. Es solo que una me pilló en el vagón correcto de mi propia vida y la otra me llegó después. Si la hubiera visto antes, ahora estarían a la inversa. Probablemente.
## Por qué esta película funciona también para alguien sin "carcelaria"
Cadena perpetua se cuenta como película carcelaria pero no es exactamente una. La cárcel es **una metáfora**, no el tema. Lo que cuenta la película es algo que le pasa a casi todo el mundo: **el riesgo de que la rutina te institucionalice**. Empiezas en un trabajo, en una ciudad, en una pareja, en una casa, en una rutina, y al principio te ves haciendo eso unos años. Veinte años después sigues ahí, y has dejado de ver alternativas porque has olvidado que existían. Eso es Shawshank.
El "no me quiero institucionalizar" es la lectura que hace de la película mucha gente que la ha visto en momentos clave: gente que está a punto de hacer un cambio profesional, gente que está saliendo de una relación larga, gente que se acaba de mudar a otra ciudad, gente que ha cambiado de oficio. Es la película de los cambios pendientes. La que te dice **que el muro que crees infranqueable no lo es tanto si llevas un martillo pequeño y mucho tiempo**.
Yo la vi por primera vez antes de un cambio profesional grande. No me la planteé como espejo en ese momento, pero a los meses, mirando atrás, me di cuenta de que la película había hecho parte del trabajo en mí. Cuando años después la volví a ver, ya en el siguiente trabajo, **funcionó como confirmación**. Y cuando la veo ahora, con cincuenta y tantos, funciona como **recordatorio**: no acomodarte, no resignarte, no convertirte en Brooks. Brooks, el bibliotecario, es uno de los personajes más tristes del cine, no porque le pase nada terrible en la película, sino por lo que **representa**: la versión de ti que se rinde. La que se queda en Shawshank por elección, después de que Shawshank haya dejado de tener barrotes.
## Pequeñas cosas que la hacen grande
He visto la película al menos siete veces y voy a soltar, sin orden, los pequeños detalles que la convierten en una obra maestra:
- **El uso de la voz en off de Red**. Casi todas las películas que se construyen con voz en off acaban siendo perezosas. Esta no. La voz en off de Red **es un personaje en sí**. Comenta, ironiza, calla cuando hay que callar. Y nunca te cuenta lo que ya estás viendo.
- **Los planos largos de Andy mirando**. Hay al menos cuatro o cinco escenas en las que la cámara se queda con Andy mirando algo, sin diálogo, sin nada que explicar. Esos planos son los que construyen al personaje.
- **El uso de la luz**. La cárcel está iluminada con un amarillo enfermizo, sucio, que pesa. Cuando la película se va de Shawshank (la playa final, la carretera), la luz cambia: es una luz blanca, abierta, casi cegadora. Esos contrastes están hechos a propósito y pegan duro.
- **Los pequeños papeles secundarios**. El director (Norton), el carcelero (Hadley), Brooks, Tommy, los hermanos. Cada uno tiene unas pocas escenas y todos están perfectamente dimensionados. No hay un solo papel sobrante.
- **El detalle del cartel**. Andy tapa su agujero detrás del muro con un cartel de mujer. Lo cambia tres veces a lo largo de la película. Las tres mujeres son **tres iconos de tres décadas distintas** (Rita Hayworth, Marilyn Monroe, Raquel Welch). Eso funciona como reloj sin necesidad de poner una fecha en pantalla. El paso del tiempo se ve en quién mira Andy desde la pared.
- **La escena del préstamo de Tommy**. Cuando un preso joven le dice a Andy que sabe quién mató a su mujer, y Andy va al director, y el director le da carpetazo, y luego pasa **lo que pasa**, y Andy entiende que no va a salir por la puerta. Esa secuencia, la cámara, los silencios, la mirada de Tim Robbins después: cinco minutos de cine puro.
- **La rotura de Brooks**. La parte más dura de la película, en mi opinión, no es nada de lo que pasa a Andy. Es la salida de Brooks de la cárcel, el viejo bibliotecario que ha pasado cincuenta años dentro, y que cuando llega al mundo de fuera no sabe vivir. La frase de Red cuando explica lo que es estar institucionalizado se aplica a Brooks como un guante.
## Lo que se aprende de esta película (sin moraleja)
Si tuviera que sacar **una sola lección** de *Cadena perpetua*, sería esta: **la fortaleza está en la paciencia, no en la fuerza**. Andy no escapa de Shawshank por inteligencia más que la de los demás (de hecho, la mayoría de presos son listos a su modo). No escapa por fuerza física (no la tiene). Escapa **porque lleva veinte años cavando**. Y porque lleva veinte años haciendo, dentro de la cárcel, la vida que pueda parecerse más a la vida que querría tener fuera.
Eso vale para muchas cosas. Vale para las relaciones de pareja largas que aguantan: lo que las hace aguantar no es la pasión inicial, es el trabajo diario. Vale para la paternidad: **lo que hace de uno un padre decente no es la noche que lo decides, son los miles de gestos pequeños que repites cada día**. Vale para el trabajo profesional: las carreras de fondo se construyen así. Vale para el aprendizaje de un instrumento, de un idioma, de un oficio.
La película es, en ese sentido, **una oda al cuentakilómetros bajo de un coche que lleva muchos años funcionando bien**. No es la oda al motor potente, ni a la salida rápida, ni al freno espectacular. Es la oda al motor que sigue.
## A quién se la recomiendo y a quién no
Se la recomiendo a:
- Cualquiera que esté en una mala época laboral, una mala época de pareja, o cualquier otro tipo de "estoy atascado".
- Quien haya tenido un amigo de los buenos durante muchos años. Le va a tocar.
- Quien tenga cincuenta y se mire para atrás y se mire para delante y se pregunte qué le queda.
- Quien tenga veinte y se esté preparando para los próximos treinta.
- Cualquiera que crea que el cine americano de los noventa fue **la última gran cosecha** del cine comercial. Porque lo fue, y esta es la prueba.
No se la recomiendo a:
- Quien necesite acción cada cinco minutos para no aburrirse. La primera media hora va lenta y si no aguantas eso, no aguantas la película.
- Quien no soporte la voz en off como recurso. Esta película es, en buena parte, una voz en off.
- Quien busque sorpresa argumental. La película tiene un giro, sí, pero el giro no es lo que la sostiene.
- Quien ya esté cansado de escuchar que es la mejor película de la historia. Si vienes con esa expectativa, te va a defraudar. No es la mejor película de la historia. Es **mi número dos**, que es una cosa bastante distinta.
## Una nota sobre Stephen King
El relato corto en el que se basa la película es de **Stephen King**, *Rita Hayworth and Shawshank Redemption*, publicado en una colección llamada *Different Seasons* en 1982. Quien conozca a King por *It* o *El resplandor* a veces se sorprende al saber que también escribe estas otras cosas: relatos contenidos, sin terror, sobre personajes muy humanos. *Cuenta conmigo* (de la misma colección, la película de Rob Reiner) y *Apt Pupil* son los otros dos relatos de ese libro. Los tres tienen versiones cinematográficas y los tres son notables, pero *Cadena perpetua* es claramente la culminación.
Darabont, el director, vino del mundo del relato corto de King. Hizo *Cadena perpetua*, luego *La milla verde* (también de King), y luego *La niebla* (también). Es **el director "kingiano por encargo"**, y *Cadena perpetua* es su gran trabajo. Las otras dos están bien (especialmente la primera) pero no llegan. Esta película tuvo todos los astros alineados.
## Cierre
Si en tu lista personal Cinema Paradiso no es la uno y *Cadena perpetua* no es la dos, no pasa nada, casi seguro que tienes razones tan buenas como las mías. En la mía, sí. Y nada más cierto que estar a punto de cumplir los cincuenta y darse cuenta de que la película que te puso en su sitio en su momento sigue funcionando ahora, en otro momento, por razones distintas. Eso es lo que define a una favorita.
El próximo post es el [23 de mayo](/post/top-10-peliculas-3-el-padrino) y voy con una saga. La conoces. La conoce tu padre. La conoce tu hija si tiene más de quince. Y lo que voy a defender es **por qué meto las dos primeras juntas como un solo puesto** y por qué la tercera no entra ni con calzador.
Si esta serie te está enganchando, te animo a poner alguna noche, esta semana, *Cadena perpetua*. Te aviso de un detalle: es una de esas películas que **mejoran al revisitarlas**. La primera vez te coge desprevenido, la segunda la disfrutas más despacio, la tercera te das cuenta de que cada plano está pensado. Si la tienes vista, dale otra vuelta. No te va a defraudar.
Hasta el día 23.
---
# Buñuelos de bacalao en El Gallo: el triángulo donde se gana o se pierde el plato
URL: https://javiervalencia.net/post/pena-flamenca-el-gallo-bunuelos-de-bacalao
Quinta entrega de la serie sobre la cocina de Paco Flores en la peña flamenca El Gallo, en Las Lagunas de Mijas. Después del [solomillo al Pedro Ximénez](/post/pena-flamenca-el-gallo-solomillo-al-pedro-ximenez), volvemos a producto humilde: los **buñuelos de bacalao**.
Si hay un plato donde el chef se desnuda, son los buñuelos. La receta es de las más simples de la cocina del sur (bacalao desalado, masa, fritura), pero hay tres variables que tienen que cuadrar simultáneamente para que el plato salga bien. **Si una de las tres falla, todo el plato se cae.** Por eso los buñuelos son un examen abierto que cualquier comensal medio puede valorar al primer bocado.
Vamos al triángulo.
## Vértice 1: el desalado del bacalao
El primer punto del triángulo, y el que más sitios estropean, es el **desalado del bacalao**. El bacalao salado, comprado en lomo o en migas, viene con cantidades enormes de sal por motivos históricos (conservación) que hoy ya no son necesarios pero que el producto mantiene. Sacarle la sal sin estropear la carne es trabajo de **víspera**, no de momento.
El desalado correcto del bacalao en migas pide:
- **36 a 48 horas en agua fría** (frigorífico, no temperatura ambiente).
- **Cambios de agua cada 6-8 horas**, retirando la sal disuelta y sustituyendo por agua limpia.
- Cantidad suficiente de agua: **diez veces el peso del bacalao**, mínimo, para que la sal tenga donde difundirse.
Los errores típicos:
- **Desalado demasiado corto** (12 horas, 24 horas). Los buñuelos salen salados. Un bocado y ya no quieres más.
- **Desalado demasiado largo** (más de 60 horas). El bacalao pierde sabor, queda insípido, los buñuelos saben a masa. También es perceptible.
- **Cambios de agua escasos** o agua templada. La sal sale lenta o desigual, y el resultado es heterogéneo: unos buñuelos saladísimos junto a otros sosos.
En El Gallo, el bacalao está **desalado al punto**: salinidad presente pero no agresiva, sabor a bacalao limpio en boca, sin la sensación de morder un cubo Maggi. Eso es trabajo de cocina ordenada, hecho con días de antelación.
## Vértice 2: la masa
El segundo vértice es **la masa**. La masa de los buñuelos de bacalao tiene que ser **ligera, esponjosa, capaz de inflar en el aceite caliente sin romperse**. Las dos escuelas principales son:
1. **Masa con harina de trigo, huevo y leche o agua** (la versión tipo crepe espesa). Más cuerpo, más densa.
2. **Masa con patata cocida triturada y huevo** (la versión cremosa, más cercana a la croqueta gruesa). Más sutil, más untuosa por dentro.
La versión de Paco está más cerca de la primera, pero con un **batido de claras** o un **levado** que aporta esponjosidad. La masa final tiene **bacalao desmigado en proporción suficiente** (no son buñuelos de masa con tropezones de bacalao, son buñuelos donde el bacalao está presente en cada bocado).
Errores frecuentes en la masa:
- **Demasiada harina**: el buñuelo sale denso, "pelota" en el interior, masa que se pega al paladar.
- **Demasiado poca harina**: el buñuelo no se sostiene, se rompe en el aceite, sale aceitoso.
- **Bacalao mal desmigado** (trozos grandes): se concentra en algunas zonas y deja vacíos en otras.
- **Reposo insuficiente**: la masa necesita reposar para que la harina absorba el líquido. Sin reposo, queda heterogénea.
La masa, bien hecha, **se trabaja con dos cucharas** (una para coger, otra para empujar al aceite) y forma bolas irregulares y rústicas. Si los buñuelos son perfectamente esféricos y uniformes, sospecha: probablemente son industriales o congelados.
## Vértice 3: el aceite
Tercer vértice, y donde más sitios fallan: **la temperatura del aceite**.
La temperatura correcta para freír buñuelos de bacalao es **entre 175 °C y 180 °C**. Más caliente y la masa se dora por fuera antes de cocerse por dentro, dejando núcleo crudo. Menos caliente y la masa absorbe aceite, se queda blanda, sale grasienta y pesada.
Aquí hay dos errores típicos:
- **Aceite demasiado caliente** porque la freidora está a tope o la sartén lleva mucho rato al fuego. Los buñuelos salen marrón oscuro por fuera, blancos y húmedos por dentro. Sabes que ha pasado al primer bocado: el exterior amarga, el interior está crudo.
- **Aceite reusado, oxidado**. Los buñuelos absorben sabores rancios del aceite anterior. Cuando un sitio reutiliza demasiado el aceite (porque ahorra, porque pasa de cambiarlo), se nota en cualquier fritura, pero en los buñuelos se nota especialmente porque la masa absorbe mucho.
El aceite, además, debe ser **de oliva** (preferiblemente virgen extra de variedad neutra, tipo arbequina o picual joven) o **de girasol alto oleico** si la economía aprieta. Aceites de girasol normales son aceptables pero saben menos. Aceites antiguos, con sedimento o con olor cargado, no se aceptan.
En El Gallo, el aceite de fritura **está limpio** y **a la temperatura justa**. Los buñuelos llegan a la mesa **dorados de manera uniforme**, no tostados, sin charco de aceite alrededor en el plato. Eso significa que la cocina cuida el aceite y respeta la temperatura.
## El resultado
Cuando los tres vértices cuadran, los buñuelos de bacalao salen como deben:
- **Por fuera**: dorado uniforme, ligeramente crujiente al primer mordisco, sin grasa en exceso visible.
- **Por dentro**: esponjoso, ligero, con bacalao desmigado distribuido en toda la masa, sin saber a salado y sin saber a soso.
- **En boca**: el primer impacto es la corteza crujiente; el segundo, la masa ligera; el tercero, el sabor del bacalao desalado al punto. Sabores claros, ordenados, sin pelearse entre ellos.
La cocina de Paco entrega exactamente eso. Cada buñuelo es **independiente**: no se aplastan unos a otros en el plato, no comparten manchas de aceite, no llevan colas de masa pegadas. Salen al plato y aguantan unos minutos antes de empezar a perder textura, pero la idea es comerlos calientes.
## Acompañamiento: el alioli, sí o no
Cuestión clásica: ¿buñuelos de bacalao con alioli, sin alioli, con limón, con qué?
Mi opinión, después de probarlos varias veces: **con alioli ligero, sí**. Pero un alioli **de verdad**, hecho con aceite, ajo y huevo, no una mayonesa con ajo en polvo añadido. El alioli serio aporta untuosidad fría que contrasta con la fritura caliente; la mayonesa con ajo aplasta el plato.
Si la opción no está disponible, **un cuarto de limón** funciona perfectamente. La acidez del limón corta la grasa y refresca el paladar entre bocado y bocado.
**Lo que evitaría**: salsas elaboradas (rosa, brava, cocktail). No están al nivel del plato.
## Maridaje: opciones limpias
Los buñuelos de bacalao piden bebida fresca y limpia. Tres opciones:
1. **Manzanilla de Sanlúcar o fino de Jerez**, bien fríos. Como con la alcachofa: la salinidad de los vinos del marco de Jerez casa con el bacalao, y la sequedad limpia la fritura. Funciona en tapa o en ración.
2. **Cerveza tipo lager bien fría** (pilsner, lager mediterránea, no IPA). El amargor del lúpulo limpia paladar entre bocado y bocado. Es probablemente la opción más popular en barra, y por motivos válidos.
3. **Albariño de Rías Baixas o blanco verdejo de Rueda**. Las acideces frescas funcionan bien con la fritura. Más estándar que las dos anteriores, igualmente correcto.
**Lo que evitaría**: tintos (cualquier tinto rompe el plato), espumosos dulces, vinos con barrica.
## Cómo pedirlos
1. **De los primeros**, no de los últimos. Los buñuelos calientes son una experiencia distinta de los buñuelos templados. La fritura pierde textura rápido. Si llegan al final de la comida y la mesa está saciada, es un desperdicio.
2. **En tapa para una mesa de dos, en ración para tres o cuatro.** La unidad razonable es 5-6 buñuelos en tapa, 10-12 en ración.
3. **Sin pan inicialmente.** Como con la brandada: aprecia el plato en limpio antes de empezar a mojar.
## Para terminar
Los buñuelos de bacalao son un plato de cocina honesta donde no hay donde esconderse. **Si la cocina los hace bien, los hace bien. Si los hace mal, no hay manera de disimular.** En El Gallo, los hacen bien. Y eso, en una zona donde la fritura industrial ha invadido demasiados bares, es un acto de resistencia.
La próxima entrega de la serie: los **bastones de berenjena con miel de caña**. Detalles que separan un plato bueno de uno memorable, y por qué este plato gancho aparente es, también, un examen.
---
# Solomillo al Pedro Ximénez en El Gallo: cuando la salsa es el plato
URL: https://javiervalencia.net/post/pena-flamenca-el-gallo-solomillo-al-pedro-ximenez
Cuarta entrega de la serie sobre la cocina de Paco Flores en la peña flamenca El Gallo, en Las Lagunas de Mijas. Después del [secreto ibérico](/post/pena-flamenca-el-gallo-secreto-iberico), seguimos con carne, pero cambiando completamente de registro. Donde el secreto pedía silencio, austeridad y plancha al punto, este plato pide lo contrario: **que la salsa hable**.
El **solomillo al Pedro Ximénez** es, en la cocina andaluza moderna, una de las preparaciones más reconocibles. Y, paradójicamente, es uno de los platos donde la diferencia entre las versiones decentes y las versiones memorables es **enorme**. Casi todo se decide fuera de la sartén del solomillo, en cómo se hace la salsa.
## Qué es el plato
El solomillo al Pedro Ximénez consiste en un trozo de **solomillo de cerdo (o de ternera, según versión)**, cocinado a la plancha, napado con una **reducción de Pedro Ximénez** ligada con caldo oscuro y, según la mano, mantequilla. La salsa es brillante, densa, dulce-salada, capaz de enroscarse en la cuchara y caer en el plato con peso visible.
La paradoja del plato está en que el solomillo, que debería ser el protagonista, **es casi un soporte**. La protagonista es la salsa. El solomillo aporta proteína y un sabor neutro que sirve de canvas para la reducción. Por eso, este plato lo gana o lo pierde la salsa. Y por eso, este plato es un examen al chef: no por la cocina del solomillo, sino por la **paciencia y técnica** que pone en la reducción.
## El Pedro Ximénez: bodega seria vs supermercado
Aquí va la tesis principal del post. El Pedro Ximénez es un vino dulce de la zona de Montilla-Moriles (y también del marco de Jerez, en menor cantidad), elaborado con uvas pasificadas al sol. Es un vino con cuerpo, con azúcar, con notas a pasa, higo seco y café tostado en las versiones largas. Es, en sí mismo, una bebida seria.
Hay dos categorías muy distintas de Pedro Ximénez en el mercado:
- **PX joven o de bodega industrial.** Etiqueta genérica, precio bajo (5-8€ la botella), color uniforme, sabor lineal: dulce, plano, cierra rápido en boca. Sirve para postres caseros. **No sirve para reducir.**
- **PX de bodega seria, con criaderas y soleras.** PX con 8, 12, 20, 30 o más años de envejecimiento. Etiquetas de bodegas que llevan generaciones haciendo este vino. Precio: a partir de 20€, fácilmente más. Color caoba, complejidad aromática, capas de sabor.
Cuando un chef reduce un PX de los segundos, la salsa **adquiere la complejidad del vino original**: notas tostadas, profundidad, capas que aparecen a medida que el plato se va comiendo. Cuando reduce uno del primer grupo, la salsa queda **dulce y plana**: caramelo de azúcar, sin matices, pesada en el postpaladar.
La diferencia es perfectamente perceptible. Quien come solomillo al PX en muchos sitios reconoce a la primera la versión barata: dulzor frontal sin profundidad, sensación de que falta algo. Y, cuando aparece la versión buena, también lo reconoce: la salsa **deja huella**.
En El Gallo, sin haberlo confirmado en cocina (es la próxima pregunta que tengo pendiente para Paco), la salsa que sirven con el solomillo está claramente en la categoría seria. Tiene capas, tiene fondo tostado, tiene esa complejidad que solo un PX de bodega da.
## La técnica: paciencia, temperatura, montar
La reducción del PX para salsa, hecha bien, es un proceso lento. En grandes líneas:
1. **Caldo oscuro de fondo.** Se parte de un fondo oscuro de carne (huesos asados, mirepoix, agua, horas de fuego). Es la base sin la que la salsa no tiene cuerpo. Si el sitio usa pastilla de caldo o caldo industrial, se nota.
2. **Reducción del PX.** El vino se pone en sartén o cazuela, se calienta y se reduce a fuego medio-bajo. Reducir significa **evaporar el agua y el alcohol**, dejando azúcares y aromas concentrados. La cantidad de PX se reduce a un tercio o un cuarto del volumen original.
3. **Mezcla con el caldo.** Una parte de la reducción se incorpora al caldo oscuro, también reducido por separado. Aquí se ajusta proporción según gusto y receta.
4. **Montar con mantequilla.** El paso final es **montar la salsa con mantequilla fría, fuera del fuego**. La mantequilla emulsiona y aporta brillo, untuosidad y un punto de grasa que redondea el dulzor. Una salsa PX sin mantequilla suele quedar mate y un poco áspera; una salsa montada brilla en el plato.
Todo esto se hace con tiempo, no con prisa. La salsa **no se hace al momento**; en una cocina seria, está preparada (o casi) antes de que entre la comanda. Cuando llega el pedido, el solomillo se cocina en plancha, se mete en la sartén con la salsa para terminar la cocción y absorber sabor, y se sirve.
## El solomillo en sí
Comparado con la salsa, el solomillo casi no merece párrafo, pero merece un par de notas:
- **Solomillo de cerdo o de ternera**: cualquiera de los dos funciona. El de cerdo es más habitual en cocina andaluza por proximidad y precio; el de ternera (de añojo o vaca) levanta el plato si la materia prima es seria.
- **Punto al gusto**: en el solomillo de cerdo, lo razonable es **al punto, jugoso pero no rosa intenso**. En el de ternera, se admite más rojo. Aquí la diferencia con el secreto ibérico: el solomillo es un corte magro, menos graso, y se seca más fácilmente. Hay que cuidarlo.
- **Espesor de corte**: medallones de uno o dos centímetros, no más. Si te llega un taco grueso, se va a quedar crudo por dentro o sobrecocido por fuera, y la salsa ya no salva.
## Acompañamientos
A diferencia del secreto, el solomillo al PX **admite y agradece guarnición**. La salsa pide algo donde recogerse:
- **Patata panadera o patata pobre**: para mojar en la salsa. Imprescindible.
- **Verde fresco**: contraste limpio que descansa la mesa.
- **Pan rústico**: mojar pan en la salsa al final del plato no es opcional, es protocolario. Que no falte pan en mesa.
Lo que **evitaría**: arroz blanco (la salsa lo empapa pero no lo eleva), pasta (no es el contexto), patatas fritas industriales (otra vez, aplastan).
## Maridaje: tres opciones
Este plato admite varios caminos en copa, según se quiera ir por dulzor o por contraste.
1. **El propio Pedro Ximénez, en copa pequeña.** No con el plato, sino **al final**, como digestivo. Un Pedro Ximénez de bodega seria, en copa de cata pequeña, después del último bocado, cierra el plato con la misma materia prima que ha hecho la salsa. Es una decisión circular y muy elegante.
2. **Tinto con cuerpo y fruta madura.** Una crianza de Ribera del Duero, un tempranillo de Rioja con barrica corta, un syrah del marco de Málaga (Sierras de Málaga) con cierta concentración. Aquí sí entra la barrica. La grasa de la salsa la pide.
3. **Oloroso seco de Jerez o Montilla-Moriles.** El maridaje más arriesgado y, para algunos paladares, el mejor. El oloroso seco contrasta con el dulzor del PX, no lo refuerza, y abre el plato de otra manera. Para quien quiera salir del binomio "carne con salsa = tinto", esta es la salida elegante.
**Lo que evitaría**: blancos jóvenes, rosados, espumosos. La salsa los borra.
## Cómo pedirlo
Tres apuntes prácticos:
1. **Pídelo en ración** si vais más de dos a la mesa. La salsa es para compartir, mojar pan y conversar. En tapa, queda corto y no llegas a apreciar todo el juego.
2. **Pregunta si es solomillo de cerdo o de ternera**. Cambia el plato. En El Gallo, según la temporada y el día, puede ser uno u otro; conviene saberlo antes de pedir.
3. **No lo pidas al principio de la comida**. Es plato denso, dulce, contundente. Lo razonable es ponerlo al final del bloque salado, justo antes de plantear postre. Si lo metes en los entrantes, mata el resto de la mesa.
## Para terminar
El solomillo al Pedro Ximénez es uno de esos platos que separa cocinas de otras. La diferencia entre la versión correcta y la versión memorable está enteramente en la **bodega del PX que ha entrado en la cocina** y en la **paciencia que ha tenido el chef** para reducir. En El Gallo, ambas cosas están bien resueltas, y eso convierte un plato muy común en un plato que merece su propia entrada en la serie.
La próxima entrega: los **buñuelos de bacalao**. Masa, desalado, temperatura del aceite. El triángulo donde se gana o se pierde uno de los platos más castigados de la cocina del sur.
---
# Las diez mejores películas de mi vida: 1. Cinema Paradiso
URL: https://javiervalencia.net/post/top-10-peliculas-1-cinema-paradiso
Llevo tiempo dándole vueltas a hacer una lista de mis diez películas favoritas. No de las mejores películas de la historia (esa lista ya la han hecho Sight and Sound y unos cuantos más, y yo no soy crítico), sino de las que más me han marcado. Las que veo cada cierto tiempo. Las que cuando aparecen en una conversación me pongo pesado defendiéndolas. Las que recomendaría a alguien si me pidiera "una película para una tarde tranquila" y supiera que tenemos los gustos parecidos.
Diez películas son pocas. Cada vez que intento cerrar la lista se cae alguna y entra otra. *Pulp Fiction* estuvo dentro y fuera tres veces antes de quedarse fuera, sin rencor. *El club de la lucha* pasó por aquí y se fue. *El laberinto del fauno* aguantó hasta el último corte. *Apocalypse Now* lleva queriendo entrar desde hace veinte años y nunca acaba de hacerlo, no porque no la quiera, sino porque cuando hago hueco para ella se cae *La vida es bella* y no me parece justo. Esto es lo que tiene una lista cerrada de diez: que es injusta. Pero es la gracia.
Lo que sí tengo clarísimo, y por eso voy a empezar la serie por aquí, es la número uno. Y no, **no voy a hacerlo en cuenta atrás**. Voy a hacerlo al revés. Voy a soltar la número uno hoy mismo, **Cinema Paradiso**, y a partir de ahí iré bajando hasta la diez. La razón es muy simple: si te tienen que enganchar diez posts seguidos, mejor que el primero sea el más importante. Si después de leer este te interesa cómo veo el cine, sigues. Si no te engancha, no merece la pena que te leas los nueve restantes. Y si ya conocías el blog y este post te parece un mero "ya está, otra vez con Cinema Paradiso", también te aviso desde el principio, así no pierdes el tiempo.
Aviso justo: ya escribí en su día un [ensayo largo dedicado solo a esta película](/post/por-que-cinema-paradiso-es-la-mejor-pelicula-jamas-hecha). Me acerco a tres mil palabras en aquel y no quiero repetirme aquí. Lo que vas a leer en este post es **otra cosa**: el sitio que ocupa Cinema Paradiso dentro de mi top diez, qué la coloca por encima de las otras nueve y qué tienen las diez en común que justifica que estén juntas en la misma serie. Si después quieres más sobre la propia película, allí está el ensayo, con detalles plano a plano, escena a escena, recuerdo a recuerdo.
## Cómo se hace una lista honesta de diez películas

Hay dos formas de hacer una lista de diez películas. Una es la lista profesional: la haces con criterio, mirando influencia histórica, técnica, la importancia del director en el cine, el peso cultural, el grado de innovación. Esa lista, si la hago yo, lleva probablemente *Ciudadano Kane*, *Vértigo*, *2001: una odisea del espacio*, *El acorazado Potemkin*, *Centauros del desierto*, *Tokyo Monogatari*, *Persona*, *Ladrón de bicicletas*, *El padrino* y *Cinema Paradiso*. Y la lista profesional la dejaríamos ahí. Es una buena lista. La voy a respetar siempre. Pero **no es mi lista**.
La mía es la otra: la lista honesta. La que no se hace con criterio sino con corazón. La que pone en la lista pelis que te han hecho llorar, que te han acompañado en momentos malos, que has visto con tus padres, con tu pareja, con tus hijas, en cines que ya no existen. La que pone *Forrest Gump* por encima de *Centauros del desierto* sin pedir perdón. La que coloca a *Cadena perpetua* en el segundo puesto aunque "objetivamente" hay películas mejores. La que mete a *Amélie* aunque media crítica francesa la tildó de cursi. La que pone *Doce hombres sin piedad* la décima no porque sea la peor de la lista (pelea por ser la mejor del lote en términos de guion) sino porque es **la que menos veces revisito**. La lista honesta tiene asteriscos, peros, contradicciones. Es como tu vida.
Cinema Paradiso entra en las dos listas. En la profesional y en la honesta. Esa es una de las primeras razones por las que está en el número uno. Hay películas que están en mi lista por sentimiento puro, y hay películas que están porque son obras maestras frías y reconocibles. Cinema Paradiso es las dos cosas a la vez, lo cual no le pasa a casi ninguna otra de la lista (a *Casablanca* sí, a *El padrino* también, al resto en distintos grados). Es una película que a la vez te llega al pecho y a la vez se sostiene en cualquier debate sobre técnica, montaje, dirección de actores y banda sonora. No tiene grietas por ningún lado. Eso es muy raro.
## Por qué la cabeza de la lista es esta y no otra

He pensado mucho cuál es exactamente la diferencia entre Cinema Paradiso y la siguiente que más quiero, *Cadena perpetua*. *Cadena perpetua* es perfecta. La he visto siete u ocho veces. Cada una me sigue funcionando. Su guion es uno de los más sólidos jamás escritos, su última media hora es uno de los mejores cierres de la historia del cine y su frase final ("espero que el Pacífico sea tan azul como en mis sueños, espero") puede competir con cualquier final. Y sin embargo no está en el número uno. ¿Por qué?
La diferencia, cuando me he sentado a desmontarla, es que *Cadena perpetua* es una película que **te conmueve mientras la ves**. Cinema Paradiso es una película que **te transforma poco a poco después**. Cuando termina *Cadena perpetua* sales del salón con los ojos llorosos y ese subidón de "qué peliculón". Cuando termina Cinema Paradiso te quedas callado un buen rato. Y lo más raro: te queda dentro durante días. La banda sonora te aparece en la cabeza a la mañana siguiente. Una secuencia te asalta cuando ves a alguien en una estación. Te acuerdas de gente con la que perdiste contacto y te dan ganas de escribirles. Eso, para mí, es la marca de la mejor película.
Hay otra razón más concreta. Cinema Paradiso trata, **en el fondo**, de una idea que me toca personalmente: la de que la persona que eres ahora se la debes a unos pocos individuos muy concretos a los que probablemente no le has dado las gracias suficientes. Alfredo es la persona que le permite a Salvatore convertirse en quien acaba siendo, y la película no se dedica a celebrarlo en vida, sino a reconocerlo cuando ya no se puede. Eso, a mis cuarenta y muchos, me resuena de una manera que no me resonaba a los treinta. La película se ha hecho más mía con los años, no menos.
Y una tercera razón, que es la más mía y la menos discutible: **es la película que puso banda sonora a un momento muy concreto de mi vida**. La vi por primera vez cuando estaba intentando entender qué quería hacer y dónde quería estar. Estaba a punto de tomar decisiones que afectarían a los siguientes treinta años (con quién iba a estar, dónde iba a vivir, a qué le iba a dedicar mi tiempo profesional). En esa ventana de tiempo, ver Cinema Paradiso fue como recibir una carta de alguien que entendía exactamente por dónde andaba yo. Eso no se compra ni se construye luego. Esa coincidencia entre película y vida, entre lo que ves y lo que estás siendo en ese momento, es irrepetible. *Cadena perpetua*, *El padrino*, *Forrest Gump* las descubrí más tarde, cuando ya estaba más asentado. Cinema Paradiso me pilló en el último vagón de mi propia juventud, cuando todavía podía cambiar las cosas.
## Lo que comparten las diez películas

Si miras las diez películas que iré soltando en las próximas dos semanas, vas a ver patrones. No es casualidad. Te lo adelanto desde aquí porque ayuda a entender por qué he descartado algunas que probablemente esperabas:
**Todas hablan de personas, no de plot.** Ninguna de las diez te engancha por el twist. La mayoría puedes ver dos veces sabiendo lo que va a pasar y siguen funcionando igual o mejor. *Cadena perpetua* sabes que se va a escapar y te sigue rompiendo cuando lo hace. *Casablanca* sabes que se van a separar y te sigue partiendo. *El padrino* sabes lo que va a hacer Michael y te sigue erizando la piel. Lo que importa en las diez no es **qué pasa** sino **a quién le pasa**. Por eso no he metido películas con giros (*El sexto sentido*, *Los sospechosos habituales*), aunque me parecen bien hechas. La grandeza, para mí, no está en el truco; está en el carácter.
**Todas tienen un peso emocional muy alto.** No hay comedias puras. Las que tienen humor (*La vida es bella*, *Forrest Gump*, *Amélie*) son comedias agridulces, que te ríes y al rato te tienes que sonar la nariz. No es que no me guste reírme, pero las películas que se quedan dentro durante años suelen ser las que te tocan algo serio. La risa pura, en mi experiencia, se evapora más rápido. La emoción no.
**Todas tienen banda sonora memorable.** Esto es accidental pero llamativo. Morricone (Cinema Paradiso, OUTIA), Newman (*Cadena perpetua*), Steiner (*Casablanca*), Williams (*La lista de Schindler*), Silvestri (*Forrest Gump*), Yann Tiersen (*Amélie*), Piovani (*La vida es bella*), Rota (*El padrino*). Bandas sonoras de las que te acuerdas, que te ponen donde estabas la primera vez que las oíste. La música hace que la película te entre por sitios que no son el ojo, y eso prolonga la huella. Las películas con BSO floja se evaporan más rápido.
**Todas son películas que se pueden ver con padres, hijas, amigos.** Esto no es trivial. Hay obras maestras que prefieres ver solo (*Mulholland Drive*, *Pequeñas mujeres* la del 94 — broma, esa no, pero ya me entiendes). Las diez de mi lista son películas que las puedes poner en el sofá un domingo por la tarde con familia y todos, cada uno desde su edad y desde su momento, sacan algo. Mi hija mayor me dijo de Cinema Paradiso: "papá, esto va de ti". Mi hija pequeña, a una edad mucho más temprana, me dijo de *Forrest Gump*: "papá, qué pena". Las dos tenían razón. Esas conversaciones, que la película desencadene tres conversaciones distintas en tres edades distintas, vale más que cualquier nota técnica.
**Todas tienen final.** No es una tontería. Hay un montón de películas modernas que se quedan abiertas, que no resuelven, que dejan al espectador en el aire. Eso a veces funciona y a veces es un truco para no comprometerse. Las diez de mi lista son películas que **acaban**. Te dejan donde te tienen que dejar. *Cinema Paradiso* termina con Salvatore mirando la cinta. *Cadena perpetua* termina en la playa de Zihuatanejo. *Casablanca* termina con la frase del comienzo de una bonita amistad. *El padrino* termina con la puerta cerrada. Cada una te entrega un final que cierra lo que la película te estaba contando. Y eso, en mi experiencia, es lo que hace que la película se quede dentro: que tú puedas cerrarla también, no que se te quede pendiente.
## Por qué hago esta serie ahora
Una pregunta razonable: ¿por qué publicar diez posts de cine en mayo de 2026, en un blog que va, en general, de tecnología, de Costa del Sol, de hijas, de comer, de Go y de bases de datos? ¿Esto a qué viene?
Tiene su lógica. Llevo varios meses publicando a diario y los lectores que me siguen empezaron a notar dos cosas. Una, que cuando me suelto sobre temas no técnicos (la peña flamenca, los chiringuitos, las hijas, el desayuno molinero) los posts les enganchan más que los técnicos, aunque digan que vienen por lo técnico. Y dos, que hace falta un punto de **personalidad** en el blog que no se ve cuando paso tres semanas escribiendo sobre PostgreSQL. Esta serie es ese punto. Diez posts seguidos sobre cine es lo más alejado de un manual de Linux que se me ocurre, y eso, después de la racha de Git y de bases de datos, viene bien.
Y hay otra razón más personal. Una de mis hijas (la pequeña) acaba de cumplir una edad en la que ya empieza a sentarse conmigo a ver pelis "de papá", y ha empezado a preguntarme cuáles le tocaba ver. La lista que estoy escribiendo aquí es, también, **la respuesta a esa pregunta**. Es lo que le voy a poner en el ordenador o en el televisor en los próximos veranos. Es la herencia ligera, no la de bienes raíces, sino la de "estas son las diez películas que tu padre considera que merecen tu tiempo". A mí me la dieron en formato VHS y luego DVD; a ella le va a llegar en formato post.
## Cinema Paradiso para alguien que no la ha visto todavía
Voy a hacer una excepción y explicar muy brevemente la película para quien aún no la haya visto, sin contar más de lo que cuenta el cartel y la primera escena. Porque si te tropiezas con esta serie y nunca has oído hablar de Cinema Paradiso, prefiero darte la entrada antes que la solución.
Cinema Paradiso es una película italiana dirigida por **Giuseppe Tornatore** y estrenada en 1988. Se sitúa en un pueblo ficticio de Sicilia, **Giancaldo**, durante varias décadas que arrancan en los años cuarenta y llegan hasta los ochenta. El protagonista es un niño, **Salvatore Di Vita**, llamado Toto, que se hace amigo del proyeccionista del cine local, un hombre mayor llamado **Alfredo**. La película cuenta la relación entre los dos y, alrededor de esa relación, cuenta la vida del pueblo, el cine como acontecimiento social, la posguerra italiana, la salida del pueblo hacia el mundo, la pérdida y el regreso. Tiene **dos versiones**: una de algo más de dos horas, que es la que se estrenó internacionalmente y la que ganó el Oscar a mejor película de habla no inglesa, y una de tres horas (*Cinema Paradiso: la versión del director*), que añade una subtrama amorosa importante.
Mi recomendación sin pestañear: **ve primero la corta**. Es la que más gente ha visto, la que tiene el ritmo más afinado y la que se queda más limpia en la memoria. La del director está bien para una segunda visita, cuando quieras saber más, pero la primera vez la corta es la perfecta.
Necesitas dos horas y un sofá. No la veas con tu pareja a las once de la noche cansado de un día largo, porque si te quedas dormido en la primera hora pierdes la mitad. Resérvale una tarde de domingo (ningún día como el domingo para esta película), pon el móvil en otra habitación y déjate llevar.
Si lloras, no luches. Lloras lo que tengas que llorar.
## Lo que viene en los próximos posts
Como te decía al principio, la serie va al revés. Hoy publico la número uno. A partir del 21 de mayo iré bajando, una a una, hasta el 1 de junio en que cierro con la décima. El orden completo, con fecha de publicación, es este:
- **21 de mayo: número 2.** Una película de los noventa que mucha gente cita como su favorita, y razón no les falta. Esperanza, paciencia, tiempo, amistad masculina sin estridencias.
- **23 de mayo: número 3.** Una saga (sí, voy a meter las dos primeras juntas, no me pidan separarlas). Probablemente la mejor obra sobre familia, poder y traición jamás filmada.
- **25 de mayo: número 4.** Una de las películas clásicas por excelencia. La definición de elegancia en blanco y negro. Sacrificio.
- **26 de mayo: número 5.** Una italiana. Padre, hijo, una historia imposible contada con una ternura insoportable.
- **28 de mayo: número 6.** Una americana. Una pluma volando. Una caja de bombones.
- **29 de mayo: número 7.** Otra de los noventa. Memoria. Una pregunta sobre qué hace humano a un humano cuando todo se rompe alrededor.
- **30 de mayo: número 8.** Otra italiana, esta vez de un director siciliano que no es Tornatore. La película más larga de la lista, y la única en la que el tiempo es, literalmente, el tema.
- **31 de mayo: número 9.** Una francesa. Color, pequeñas alegrías, mirar el mundo torcido. La que más sonríe de las diez.
- **1 de junio: número 10.** Cierro con la más vieja. Blanco y negro otra vez. Una habitación. Doce hombres. Calor.
Si te he intrigado con alguna, suscríbete al RSS y te van llegando. Si después de leer este post ya sabes que esta serie no te va, no pasa nada, dentro de dos semanas vuelvo a hablar de PostgreSQL y volvemos al programa habitual.
## Una última cosa, antes de cerrar
A veces, cuando uno hace una lista de favoritos, se siente la necesidad de **disculparse** por no incluir a Kubrick, a Bergman, a Kurosawa, a Scorsese (sí, está en la lista pero por *Goodfellas*, no, espera, *Goodfellas* no entra; me explico en otro post), a Hitchcock, a Tarantino, a Almodóvar, a los Coen, a Linklater, a Malick. Y a tantos otros que se quedan fuera. No me voy a disculpar. Una lista de diez **es** lo que una lista de diez tiene que ser: dejar fuera mucho más de lo que mete. Si la hiciera de cien sería más justa, pero también sería ilegible.
Lo que sí quiero decir es que esta lista no pretende ser definitiva ni objetiva. Es **mía**, escrita en mayo de 2026, con cuarenta y muchos años, después de casi cuarenta años viendo cine. Si la hubiera hecho hace diez, habría salido distinta. Si la hago dentro de diez, va a salir otra. Lo único que tengo claro es que la número uno no se va a mover. Y me apuesto contigo lo que quieras a que **esa** sí se queda.
Cinema Paradiso es la primera porque es la primera. Punto. No hay más debate, al menos en esta cabeza. Y dentro de cinco días empezamos a bajar.
Si no la has visto, búscala. Si la has visto pero hace años, vuélvela a ver antes del 1 de junio, y luego me cuentas. Si la conoces de memoria, te dejo aquí dos minutos del [tema principal de Morricone](https://www.youtube.com/results?search_query=ennio+morricone+cinema+paradiso+main+theme) (link de búsqueda, no link directo, por si acaso te llega a este post años más tarde y los enlaces caducan): ponlo, cierra los ojos, y déjate llevar treinta segundos. Esa es la versión más concentrada de por qué esta película es mi número uno.
Nos vemos el día 21, con la número dos.
---
# Cluster MariaDB master-master en Debian 13: instalación paso a paso desde el repo oficial
URL: https://javiervalencia.net/post/cluster-mariadb-master-master-debian-13-paso-a-paso
*Tiempo de lectura estimado: 16 minutos.*
En la [quinta entrega de la serie MariaDB desde cero](/post/mariadb-desde-cero-v-replicacion-galera-y-produccion) pasé por encima de Galera, replicación asíncrona, backups y producción a vista de pájaro. Esta vez bajo al detalle: montar **un cluster de 3 nodos en Debian 13 trixie** desde cero, usando el **repo oficial de MariaDB Foundation**, y demostrar que funciona con pruebas reales (caídas, recuperación, conflictos de escritura, latencia).
Sobre la terminología: en el mundo MySQL/MariaDB *master-master* significa históricamente "dos primarias asíncronas en círculo". Aquí lo uso en el sentido más amplio y más usado hoy: **multi-master síncrono con Galera**. Es lo que la gente busca cuando dice "cluster master-master" en 2026 y es lo que tiene sentido montar en producción.
## Por qué Galera y por qué tres nodos
Galera resuelve dos problemas de la replicación clásica de un solo plumazo:
- **Cualquier nodo acepta escrituras.** No hay rol "primario" fijo, así que el failover deja de ser un procedimiento manual.
- **El commit es síncrono.** Cuando la transacción se confirma, los tres nodos ya la tienen. No hay ventana de pérdida de datos.
¿Por qué tres y no dos? Por el **quórum**. Galera necesita mayoría (`N/2 + 1`) para seguir aceptando escrituras. Con dos nodos, perder uno te deja con 1 de 2 (sin mayoría) y el cluster se bloquea. Con tres nodos, perder uno te deja con 2 de 3 y el cluster sigue. Es la diferencia entre "alta disponibilidad" y "alta disponibilidad de verdad".
Si solo tienes presupuesto para dos máquinas reales, una solución habitual es añadir un **garbd** (Galera Arbitrator) en una tercera VM minúscula: aporta voto sin almacenar datos. Pero aquí montamos tres nodos completos.
## Topología
Tres VMs Debian 13 trixie en una red privada `/24`. Todo lo que sigue asume estas IPs y nombres; sustituye por los tuyos:
| Hostname | IP | Rol |
|-------------|---------------|---------------|
| `db-01.lan` | `10.10.0.11` | bootstrap |
| `db-02.lan` | `10.10.0.12` | nodo |
| `db-03.lan` | `10.10.0.13` | nodo |
Recursos por VM (mínimo razonable): 2 vCPU, 4 GB RAM, 40 GB de disco. Para producción con carga real, dimensiona según `innodb_buffer_pool_size`.
Puertos que usa Galera:
- **3306/tcp** — cliente SQL.
- **4567/tcp+udp** — replicación entre nodos (gcomm).
- **4568/tcp** — transferencia incremental de estado (IST).
- **4444/tcp** — transferencia completa de estado (SST).
## Preparación del sistema (en los 3 nodos)
Lo siguiente se hace **en cada nodo**, idéntico salvo el hostname y la IP.
### Hostnames y DNS local
Sin DNS interno, el `/etc/hosts` es el mejor amigo. En los tres nodos:
```bash
sudo hostnamectl set-hostname db-01.lan # ajusta por nodo
sudo tee -a /etc/hosts <<'EOF'
10.10.0.11 db-01.lan db-01
10.10.0.12 db-02.lan db-02
10.10.0.13 db-03.lan db-03
EOF
```
Compruébalo:
```bash
for n in db-01 db-02 db-03; do ping -c1 -W1 $n >/dev/null && echo "$n OK"; done
# db-01 OK
# db-02 OK
# db-03 OK
```
### Reloj sincronizado
Galera detesta el reloj suelto. Debian 13 trae `systemd-timesyncd` activado de serie, pero verifícalo:
```bash
timedatectl status | grep -E "System clock|NTP service"
# System clock synchronized: yes
# NTP service: active
```
Si por lo que sea estuviera en `no`/`inactive`, arréglalo antes de continuar:
```bash
sudo timedatectl set-ntp true
```
### Firewall
Si tienes `nftables` o `ufw` activo, abre los puertos entre los nodos (no al mundo). Con `nftables` directo:
```bash
sudo nft add rule inet filter input ip saddr 10.10.0.0/24 \
tcp dport { 3306, 4567, 4568, 4444 } accept
sudo nft add rule inet filter input ip saddr 10.10.0.0/24 \
udp dport 4567 accept
```
Con `ufw`:
```bash
sudo ufw allow from 10.10.0.0/24 to any port 3306 proto tcp
sudo ufw allow from 10.10.0.0/24 to any port 4567
sudo ufw allow from 10.10.0.0/24 to any port 4568 proto tcp
sudo ufw allow from 10.10.0.0/24 to any port 4444 proto tcp
```
**No abras 3306 al exterior**. El acceso de aplicaciones debe pasar por una VIP, ProxySQL o MaxScale, y el tráfico entre nodos vive en la red privada.
### AppArmor
Debian 13 mantiene AppArmor activo. El paquete de MariaDB instala su propio perfil, pero ten a mano `aa-status` por si algún directorio custom (datadir alternativo, por ejemplo) requiere ajuste:
```bash
sudo aa-status | grep mariadbd
```
## Repo oficial de MariaDB
Debian 13 trixie incluye MariaDB en sus repos, pero queremos la versión que elijamos nosotros y soporte directo del upstream. La forma limpia es usar el script oficial de MariaDB Foundation, que añade el repo correcto según la distro y la versión.
Vamos a instalar **MariaDB 11.4 LTS** (soporte hasta 2029, estable y muy probada con Galera):
```bash
sudo apt update
sudo apt install -y curl ca-certificates apt-transport-https gnupg
curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup \
| sudo bash -s -- --mariadb-server-version="mariadb-11.4" --skip-maxscale
```
Salida esperada:
```
# [info] Repository file successfully written to /etc/apt/sources.list.d/mariadb.list
# [info] Adding trusted package signing keys...
# [info] Successfully added trusted package signing keys
# [info] Cleaning package cache...
```
Comprueba el repo añadido:
```bash
cat /etc/apt/sources.list.d/mariadb.sources
# X-Repolib-Name: MariaDB
# Types: deb
# URIs: https://deb.mariadb.org/11.4/debian
# Suites: trixie
# Components: main
# Signed-By: /etc/apt/keyrings/mariadb-keyring.pgp
```
## Instalación de MariaDB y Galera
El paquete `mariadb-server` ya trae el proveedor wsrep (Galera) integrado. Aún así conviene instalar `galera-4` y `mariadb-backup` (necesario para SST con `mariabackup`):
```bash
sudo apt update
sudo apt install -y mariadb-server mariadb-backup galera-4
```
Verifica versiones:
```bash
mariadb --version
# mariadb from 11.4.5-MariaDB, client 15.2 for debian-linux-gnu (x86_64)
dpkg -l | grep -E "mariadb-server|galera-4|mariadb-backup" | awk '{print $2, $3}'
# galera-4 26.4.21-deb13
# mariadb-backup 1:11.4.5+maria~deb13
# mariadb-server 1:11.4.5+maria~deb13
```
Justo después de instalar, el servicio arranca con la config por defecto en standalone. Lo paramos en los tres nodos antes de configurar Galera:
```bash
sudo systemctl stop mariadb
sudo systemctl status mariadb --no-pager | head -3
# ● mariadb.service - MariaDB 11.4.5 database server
# Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; preset: enabled)
# Active: inactive (dead)
```
### mariadb-secure-installation (antes del cluster)
Lo hacemos **una vez por nodo** mientras el servicio está aún en modo standalone (lo levantamos un momento, securizamos, lo paramos):
```bash
sudo systemctl start mariadb
sudo mariadb-secure-installation
```
Responde:
- `Enter current password for root`: vacío (autenticación por socket en Debian).
- `Switch to unix_socket authentication?`: **n** (ya está activo desde 10.4; redundante).
- `Change the root password?`: **n** (root local, sin contraseña, autenticación por socket; más seguro que cualquier contraseña).
- `Remove anonymous users?`: **Y**.
- `Disallow root login remotely?`: **Y**.
- `Remove test database?`: **Y**.
- `Reload privilege tables now?`: **Y**.
Para el servicio antes de configurar Galera:
```bash
sudo systemctl stop mariadb
```
## Configuración Galera
Crea el fichero `/etc/mysql/mariadb.conf.d/60-galera.cnf` en **los tres nodos**. La mayor parte es idéntica; las dos últimas líneas (`wsrep_node_address` y `wsrep_node_name`) son específicas de cada nodo.
```ini
[galera]
# Activación
wsrep_on = ON
wsrep_provider = /usr/lib/galera/libgalera_smm.so
# Identidad del cluster
wsrep_cluster_name = "blog_cluster"
wsrep_cluster_address = "gcomm://10.10.0.11,10.10.0.12,10.10.0.13"
# Identidad del nodo (ajusta en cada máquina)
wsrep_node_address = "10.10.0.11"
wsrep_node_name = "db-01"
# SST (State Snapshot Transfer)
wsrep_sst_method = mariabackup
wsrep_sst_auth = "sstuser:CAMBIA_ESTO"
# Requisitos de Galera
binlog_format = ROW
default_storage_engine = InnoDB
innodb_autoinc_lock_mode = 2
# Aceptar conexiones de otros nodos
bind_address = 0.0.0.0
# Tuning razonable de partida
wsrep_slave_threads = 4
wsrep_provider_options = "gcache.size=512M; gcs.fc_limit=128"
```
Notas:
- `gcomm://...` con todas las IPs es lo correcto en arranque normal. En el bootstrap se usa una variante distinta (la veremos en un momento).
- `innodb_autoinc_lock_mode = 2` es obligatorio en Galera. Con valor 1 las escrituras concurrentes en tablas con `AUTO_INCREMENT` se serializan de forma que rompe el cluster.
- `gcache.size=512M` permite que un nodo que estuvo caído pueda recuperarse con IST (transferencia incremental) si vuelve dentro de la ventana. Si tarda más, hará SST completa.
En `db-02.lan`:
```ini
wsrep_node_address = "10.10.0.12"
wsrep_node_name = "db-02"
```
En `db-03.lan`:
```ini
wsrep_node_address = "10.10.0.13"
wsrep_node_name = "db-03"
```
### Usuario SST
`mariabackup` necesita un usuario en MariaDB para hacer las transferencias de estado entre nodos. Lo creamos **solo en el primer nodo** (después se replica solo); arrancamos brevemente standalone, lo creamos, y paramos:
```bash
sudo systemctl start mariadb
sudo mariadb <<'SQL'
CREATE USER 'sstuser'@'localhost' IDENTIFIED BY 'CAMBIA_ESTO';
GRANT PROCESS, RELOAD, LOCK TABLES, BINLOG MONITOR, REPLICA MONITOR
ON *.* TO 'sstuser'@'localhost';
GRANT SELECT ON mysql.* TO 'sstuser'@'localhost';
FLUSH PRIVILEGES;
SQL
sudo systemctl stop mariadb
```
La contraseña tiene que coincidir con la de `wsrep_sst_auth` del fichero `60-galera.cnf`.
## Bootstrap del cluster
El bootstrap es **solo la primera vez en la vida del cluster**, y solo en **uno** de los nodos. El elegido inicia el cluster en estado `Primary` con `wsrep_cluster_address = gcomm://` (sin IPs, indicando que es la semilla).
En `db-01.lan`:
```bash
sudo galera_new_cluster
```
Este wrapper de Debian lanza `mariadbd` con `--wsrep-new-cluster`. Verifica:
```bash
sudo systemctl status mariadb --no-pager | head -5
# ● mariadb.service - MariaDB 11.4.5 database server
# Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; preset: enabled)
# Active: active (running) since Fri 2026-05-15 09:42:11 CEST; 4s ago
sudo mariadb -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
# +--------------------+-------+
# | Variable_name | Value |
# +--------------------+-------+
# | wsrep_cluster_size | 1 |
# +--------------------+-------+
```
Un nodo solo, en estado primary. Ahora levantamos los otros dos como un servicio normal.
En `db-02.lan` y `db-03.lan` (uno tras otro, no en paralelo):
```bash
sudo systemctl start mariadb
```
La primera vez harán SST desde `db-01` usando `mariabackup`. En el log lo ves claro:
```bash
sudo journalctl -u mariadb -n 30 --no-pager | grep -E "WSREP|SST"
# ... WSREP: Member 0.0 (db-02) requested state transfer from '*any*'
# ... WSREP: Running: 'wsrep_sst_mariabackup --role 'joiner' ...'
# ... WSREP: SST received: ...
# ... WSREP: Member 0.0 (db-02) synced with group.
```
Cuando los tres están arriba:
```bash
sudo mariadb -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
# +--------------------+-------+
# | Variable_name | Value |
# +--------------------+-------+
# | wsrep_cluster_size | 3 |
# +--------------------+-------+
```
## Verificación del cluster
Las cuatro variables que miro siempre, en cada nodo:
```bash
sudo mariadb -e "
SHOW STATUS WHERE Variable_name IN (
'wsrep_cluster_size',
'wsrep_cluster_status',
'wsrep_ready',
'wsrep_local_state_comment'
);"
# +---------------------------+----------+
# | Variable_name | Value |
# +---------------------------+----------+
# | wsrep_cluster_size | 3 |
# | wsrep_cluster_status | Primary |
# | wsrep_local_state_comment | Synced |
# | wsrep_ready | ON |
# +---------------------------+----------+
```
Los cuatro valores tienen que ser los de arriba en los tres nodos. Si uno se queda en `Donor/Desynced` un rato es normal mientras envía SST a otro; debe volver a `Synced` en segundos.
También útil:
```bash
sudo mariadb -e "SHOW STATUS LIKE 'wsrep_incoming_addresses';"
# +--------------------------+-----------------------------------------------------+
# | Variable_name | Value |
# +--------------------------+-----------------------------------------------------+
# | wsrep_incoming_addresses | 10.10.0.11:3306,10.10.0.12:3306,10.10.0.13:3306 |
# +--------------------------+-----------------------------------------------------+
```
## Pruebas reales de funcionamiento
Aquí viene lo interesante: demostrar que el cluster hace lo que dice. Todas las pruebas son reproducibles con la instalación de arriba.
### Prueba 1: escritura en un nodo, lectura en los otros dos
En `db-01`:
```sql
CREATE DATABASE demo;
USE demo;
CREATE TABLE notas (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
contenido VARCHAR(200) NOT NULL,
origen VARCHAR(32) NOT NULL,
ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO notas (contenido, origen) VALUES ('hola desde db-01', @@hostname);
```
Inmediatamente en `db-02`:
```sql
SELECT * FROM demo.notas;
-- +----+------------------+--------+---------------------+
-- | id | contenido | origen | ts |
-- +----+------------------+--------+---------------------+
-- | 1 | hola desde db-01 | db-01 | 2026-05-15 09:51:02 |
-- +----+------------------+--------+---------------------+
```
Y en `db-03`:
```sql
SELECT * FROM demo.notas;
-- (mismo resultado)
```
### Prueba 2: escritura en cada nodo
Para que sea master-master de verdad, todos los nodos tienen que aceptar escrituras:
```bash
# Desde el host de control, lanzando contra cada nodo
for n in db-01 db-02 db-03; do
mariadb -h $n -uroot -e \
"INSERT INTO demo.notas (contenido, origen) VALUES ('eco', @@hostname);"
done
```
Lectura en cualquier nodo:
```sql
SELECT id, origen, ts FROM demo.notas ORDER BY id;
-- +----+--------+---------------------+
-- | id | origen | ts |
-- +----+--------+---------------------+
-- | 1 | db-01 | 2026-05-15 09:51:02 |
-- | 4 | db-01 | 2026-05-15 09:52:31 |
-- | 7 | db-02 | 2026-05-15 09:52:31 |
-- | 10 | db-03 | 2026-05-15 09:52:31 |
-- +----+--------+---------------------+
```
Fíjate en los `id`: saltan de 3 en 3 (1, 4, 7, 10). Es **lo esperado**: Galera asigna `auto_increment_increment = 3` (número de nodos) y un `auto_increment_offset` distinto por nodo. Así dos nodos nunca generan el mismo `id` sin coordinarse. Verifica:
```sql
SHOW VARIABLES LIKE 'auto_increment_%';
-- +--------------------------+-------+
-- | Variable_name | Value |
-- +--------------------------+-------+
-- | auto_increment_increment | 3 |
-- | auto_increment_offset | 1 | -- en db-01 (2 en db-02, 3 en db-03)
-- +--------------------------+-------+
```
### Prueba 3: conflicto de escritura
Galera resuelve los conflictos con **first commit wins** (certificación optimista). Si dos nodos actualizan la misma fila a la vez, uno gana y el otro recibe error `1213` (deadlock) en el `COMMIT`.
En dos terminales en paralelo, una contra `db-01` y otra contra `db-02`:
```sql
-- Terminal A (db-01) -- Terminal B (db-02)
START TRANSACTION; START TRANSACTION;
UPDATE demo.notas UPDATE demo.notas
SET contenido='gana A' WHERE id=1; SET contenido='gana B' WHERE id=1;
COMMIT; COMMIT;
-- Query OK, 1 row affected -- ERROR 1213 (40001): Deadlock found
-- -- when trying to get lock; try
-- -- restarting transaction
```
La fila queda con el valor de A. La aplicación tiene que **reintentar** las transacciones que reciben 1213. Es la misma disciplina que ya aplicarías con `SERIALIZABLE` en PostgreSQL.
### Prueba 4: caída de un nodo (2/3 sigue con quórum)
Tirar `db-02` por las bravas:
```bash
ssh db-02 sudo systemctl stop mariadb
```
Desde `db-01`:
```sql
SHOW STATUS LIKE 'wsrep_cluster_size';
-- +--------------------+-------+
-- | Variable_name | Value |
-- +--------------------+-------+
-- | wsrep_cluster_size | 2 |
-- +--------------------+-------+
SHOW STATUS LIKE 'wsrep_cluster_status';
-- | wsrep_cluster_status | Primary |
INSERT INTO demo.notas (contenido, origen) VALUES ('aún vivo', @@hostname);
-- Query OK, 1 row affected (0.003 sec)
```
Sigue aceptando escrituras. Quórum 2/3 = mayoría.
### Prueba 5: recuperación con IST
Vuelve a levantar `db-02`:
```bash
ssh db-02 sudo systemctl start mariadb
ssh db-02 sudo journalctl -u mariadb -n 20 --no-pager | grep WSREP | tail -5
# ... WSREP: Member 1.0 (db-02) requested state transfer from '*any*'
# ... WSREP: IST receiver addr using tcp://10.10.0.12:4568
# ... WSREP: IST received: 8023-8047
# ... WSREP: 0.0 (db-01): State transfer to 1.0 (db-02) complete.
# ... WSREP: Member 1.0 (db-02) synced with group.
```
IST: solo se transfirieron las transacciones que se perdió mientras estaba caído. Rapidísimo. Si el nodo hubiera estado caído más tiempo del que cubre `gcache.size`, Galera caería automáticamente a SST completa (más lenta pero igual de automática).
```sql
-- Desde db-02, ya sincronizado:
SELECT contenido FROM demo.notas WHERE contenido='aún vivo';
-- +----------+
-- | contenido|
-- +----------+
-- | aún vivo |
-- +----------+
```
Recibió la fila que se insertó mientras estaba abajo. Sin intervención manual.
### Prueba 6: caída de dos nodos (sin quórum)
Tira `db-02` y `db-03`:
```bash
ssh db-02 sudo systemctl stop mariadb
ssh db-03 sudo systemctl stop mariadb
```
Desde `db-01`:
```sql
SHOW STATUS LIKE 'wsrep_cluster_status';
-- | wsrep_cluster_status | non-Primary |
INSERT INTO demo.notas (contenido, origen) VALUES ('???', @@hostname);
-- ERROR 1047 (08S01): WSREP has not yet prepared node for application use
```
Galera **bloquea las escrituras** al perder mayoría. Es lo que quieres: prefiere parar a permitir un split-brain. Las lecturas también se bloquean por defecto; si quieres permitirlas se puede ajustar `wsrep_dirty_reads = ON`, pero piensatelo dos veces.
Para recuperar, lo correcto es **levantar primero los nodos que apagaste** (no rebootstrappear `db-01` a lo loco — eso fuerza una primaria nueva y puede provocar pérdida si las otras tienen datos más recientes):
```bash
ssh db-02 sudo systemctl start mariadb
ssh db-03 sudo systemctl start mariadb
```
En cuanto cualquiera de los dos vuelve y forma quórum con `db-01`, el cluster vuelve a estado `Primary` y `db-01` recupera escrituras. Verifica:
```sql
SHOW STATUS LIKE 'wsrep_cluster_size';
-- | wsrep_cluster_size | 3 |
SHOW STATUS LIKE 'wsrep_cluster_status';
-- | wsrep_cluster_status | Primary |
```
### Prueba 7: latencia de escritura con sysbench
Lo síncrono cuesta. Vamos a medir cuánto. Instala `sysbench`:
```bash
sudo apt install -y sysbench
```
Prepara la carga (10 tablas, 100 000 filas cada una):
```bash
sysbench oltp_write_only \
--mysql-host=db-01 --mysql-user=root \
--tables=10 --table-size=100000 prepare
```
Lanza 60 segundos de escrituras puras con 16 hilos:
```bash
sysbench oltp_write_only \
--mysql-host=db-01 --mysql-user=root \
--tables=10 --table-size=100000 \
--threads=16 --time=60 --report-interval=10 run
```
Salida típica en 3 nodos en la misma LAN (1 Gb/s, sin saturar nada):
```
[ 10s ] thds: 16 tps: 412.3 qps: 2473.8 (r/w/o: 0.0/2061.5/412.3)
[ 20s ] thds: 16 tps: 418.9 qps: 2513.4 (r/w/o: 0.0/2094.5/418.9)
...
SQL statistics:
transactions: 25018 (416.85 per sec.)
queries: 150108 (2501.10 per sec.)
Latency (ms):
min: 8.42
avg: 38.34
95th percentile: 58.99
max: 148.27
```
Compáralo con la misma prueba contra un nodo standalone:
```
[ 10s ] thds: 16 tps: 1827.4 qps: 10964.3
...
Latency (ms):
avg: 8.74
95th percentile: 13.46
```
Cuatro o cinco veces más latencia y un quinto del throughput. Es **el coste de la durabilidad multi-DC síncrona**. Si esto te asusta, replanteate si necesitas Galera de verdad o si te basta con replicación asíncrona + failover automático. Y si lo necesitas, dimensiona en consecuencia: red baja latencia, discos NVMe y `gcache` grande.
Limpia:
```bash
sysbench oltp_write_only --mysql-host=db-01 --mysql-user=root \
--tables=10 cleanup
```
## Operaciones del día a día
### Rolling restart (sin downtime)
Reinicia un nodo cada vez, esperando a que vuelva a `Synced` antes del siguiente:
```bash
for n in db-01 db-02 db-03; do
ssh $n sudo systemctl restart mariadb
until ssh $n "sudo mariadb -BNe \"SHOW STATUS LIKE 'wsrep_local_state_comment';\" \
| grep -q Synced"; do sleep 2; done
echo "$n OK"
done
```
### Añadir un cuarto nodo
1. Instala MariaDB y `galera-4` igual que en los primeros tres.
2. Pon su IP en `wsrep_cluster_address` **de todos los nodos** (y añade el nuevo `wsrep_node_address`/`wsrep_node_name` en el nuevo).
3. Arranca con `systemctl start mariadb`. Hará SST desde uno de los nodos existentes.
Recuerda mantener el cluster con **número impar** de nodos (3, 5, 7) si quieres preservar quórum claro. Con 4 nodos, perder 2 te deja 2/4 (sin mayoría).
### Retirar un nodo
Para retirar `db-03` definitivamente:
```bash
ssh db-03 sudo systemctl stop mariadb
ssh db-03 sudo systemctl disable mariadb
```
Quita su IP de `wsrep_cluster_address` en los nodos restantes. No requiere reinicio del cluster; el cambio se aplica en el próximo restart de cada nodo.
### Backups
`mariabackup` funciona perfectamente con Galera. Hazlo desde **un solo nodo** (idealmente uno no productivo o uno marcado como *backup donor*):
```bash
sudo mariabackup --backup \
--target-dir=/var/backups/mariadb/$(date +%F-%H%M) \
--user=sstuser --password=CAMBIA_ESTO
# Después, "preparar" el backup
sudo mariabackup --prepare \
--target-dir=/var/backups/mariadb/2026-05-15-1042
```
Sube los `.xb` a almacenamiento externo (S3, B2, lo que tengas). Restore probado mensualmente, como siempre.
## Frontal: ProxySQL o MaxScale
Galera te da el cluster, pero la app necesita un punto único de entrada. Las dos opciones razonables:
- **ProxySQL**: muy ligero, configuración en runtime vía SQL, lo que prefiero para la mayoría.
- **MaxScale**: producto de MariaDB Corporation, más integrado pero con licencia BSL para producción a partir de cierto tamaño.
Una config mínima de ProxySQL para repartir lecturas entre los tres y mandar escrituras al "writer principal" (un nodo elegido para reducir conflictos de certificación):
```sql
INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES
(10, '10.10.0.11', 3306),
(20, '10.10.0.12', 3306),
(20, '10.10.0.13', 3306);
INSERT INTO mysql_galera_hostgroups
(writer_hostgroup, reader_hostgroup, max_writers, writer_is_also_reader, active)
VALUES (10, 20, 1, 1, 1);
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
```
ProxySQL monitoriza Galera con `mysql_galera_hostgroups` y reasigna escrituras automáticamente si el writer cae.
## Lo que no te he dicho (y tienes que mirar)
- **TLS entre nodos** (`wsrep_provider_options="socket.ssl_..."`). Imprescindible si los nodos van entre data centers por enlace público o compartido.
- **Backups encriptados** con `--encrypt`.
- **Audit log** (plugin `server_audit`) para cumplimiento.
- **Monitorización** con Prometheus + `mysqld_exporter` (el exporter tiene métricas específicas de wsrep).
- **Particionado** de tablas grandes para que el SST sea más manejable.
- **Selección de versión LTS** según tu ventana de soporte. 11.4 LTS cubre hasta 2029; 11.8 LTS llegará más lejos cuando salga.
Para el panorama amplio (replicación asíncrona, GTID, semi-sync, checklist de producción) consulta la [quinta entrega de la serie MariaDB desde cero](/post/mariadb-desde-cero-v-replicacion-galera-y-produccion). Para el resto de la serie:
- [I: instalación y primeros pasos](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos)
- [II: storage engines, tipos y restricciones](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones)
- [III: consultas, CTEs y window functions](/post/mariadb-desde-cero-iii-consultas-ctes-y-window-functions)
- [IV: índices, EXPLAIN y tuning](/post/mariadb-desde-cero-iv-indices-explain-y-tuning)
- [V: replicación, Galera y producción](/post/mariadb-desde-cero-v-replicacion-galera-y-produccion)
Si te montas el cluster siguiendo esto y se atasca en algo (típicamente el SST inicial entre nodos por temas de firewall o de versión), escríbeme. Es de esas cosas donde un par de pares de ojos ahorran horas.
---
# MariaDB desde cero (V): replicación, Galera y producción
URL: https://javiervalencia.net/post/mariadb-desde-cero-v-replicacion-galera-y-produccion
*Quinta y última entrega de la serie **[MariaDB desde cero a pro](/search?tag=mariadb-desde-cero)**. Tiempo de lectura estimado: 14 minutos.*
Último tramo. Has aprendido a instalar MariaDB ([I](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos)), diseñar esquemas con storage engines y tipos adecuados ([II](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones)), escribir consultas modernas ([III](/post/mariadb-desde-cero-iii-consultas-ctes-y-window-functions)) y diagnosticar rendimiento ([IV](/post/mariadb-desde-cero-iv-indices-explain-y-tuning)). Ahora toca lo que define "estar en producción": **replicación**, **Galera**, **backups**, **monitorización** y **seguridad**.
Comparativa honesta: ponerlo todo a funcionar en MariaDB es más artesanal que en un PostgreSQL con Patroni o un RDS gestionado. No es más difícil; es distinto. Te paga en flexibilidad.
## Binlog: la base de todo
MariaDB mantiene un **binary log** (binlog): un log secuencial de cambios que sirve para replicación y *point-in-time recovery*. Activarlo es requisito para todo lo serio.
```ini
[mariadb]
server_id = 1
log_bin = /var/log/mysql/mariadb-bin
binlog_format = ROW # ROW es el estándar moderno
binlog_row_image = MINIMAL # menos espacio en replicación
expire_logs_days = 7
sync_binlog = 1 # durable, a costa de algo de rendimiento
```
Formatos de binlog:
- **STATEMENT**: guarda el SQL. Compacto, pero problemático con funciones no deterministas.
- **ROW**: guarda los cambios fila a fila. Grande, pero completamente determinista. **Usa este.**
- **MIXED**: mezcla. Evitable.
## Replicación asíncrona
La replicación "clásica": una primaria y una o varias réplicas que aplican los cambios después. Desde hace años, lo estándar es usar **GTID** (Global Transaction ID), que identifica cada transacción globalmente y hace trivial el failover y el reposicionamiento.
### Config en la primaria
```ini
[mariadb]
server_id = 1
log_bin = /var/log/mysql/mariadb-bin
binlog_format = ROW
gtid_domain_id = 1
gtid_strict_mode = ON
log_slave_updates = ON
```
Crear el usuario para las réplicas:
```sql
CREATE USER 'repl'@'10.0.%.%' IDENTIFIED BY 'xxx';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'10.0.%.%';
```
### Config en la réplica
```ini
[mariadb]
server_id = 2
log_bin = /var/log/mysql/mariadb-bin # también en réplicas
binlog_format = ROW
gtid_domain_id = 1
gtid_strict_mode = ON
log_slave_updates = ON
read_only = ON
```
Y desde SQL:
```sql
CHANGE MASTER TO
MASTER_HOST='primary.internal',
MASTER_USER='repl',
MASTER_PASSWORD='xxx',
MASTER_USE_GTID=current_pos;
START SLAVE;
SHOW SLAVE STATUS\G
```
Las líneas clave en `SHOW SLAVE STATUS`:
- `Slave_IO_Running` y `Slave_SQL_Running`: deben ser `Yes`.
- `Seconds_Behind_Master`: lag aproximado.
- `Gtid_IO_Pos`: posición actual en términos de GTID.
- `Last_Error` y `Last_SQL_Error`: si algo ha roto.
### Replicación paralela
Desde MariaDB 10, la réplica puede aplicar cambios en paralelo. En `postgresql.conf` de la réplica:
```ini
slave_parallel_mode = optimistic
slave_parallel_threads = 8
slave_parallel_workers = 8
```
Esto ayuda enormemente cuando la réplica iba quedándose atrás con cargas de escritura fuertes.
### Semi-sync
Asíncrona pura tiene un riesgo claro: si la primaria cae antes de que el binlog llegue a las réplicas, los commits recientes se pierden. **Semi-sync** mitiga: la primaria espera al ACK de al menos una réplica antes de confirmar al cliente.
```ini
# En primaria
rpl_semi_sync_master_enabled = ON
rpl_semi_sync_master_timeout = 10000 # 10s antes de caer a async
# En réplicas
rpl_semi_sync_slave_enabled = ON
```
No es garantía total, pero reduce muchísimo la ventana de pérdida.
## Galera Cluster: replicación síncrona multi-master
Galera es una tecnología de replicación síncrona integrada en MariaDB (paquete `mariadb-server` incluye el *wsrep provider* de serie). Características:
- **Multi-master**: todos los nodos aceptan escrituras.
- **Síncrona**: un commit no se confirma hasta que todos los nodos lo han aceptado.
- **Consistencia**: no hay lag de replicación (sí algo de latencia de commit).
- **Tolerancia**: con quórum (N/2 + 1), el cluster sigue funcionando.
Ideal para:
- Aplicaciones que exigen HA síncrona entre dos o tres data centers cercanos.
- Escenarios donde el downtime es inaceptable y toleras algo de latencia añadida en escritura.
### Config mínima
En `/etc/mysql/mariadb.conf.d/60-galera.cnf`:
```ini
[galera]
wsrep_on = ON
wsrep_provider = /usr/lib/galera/libgalera_smm.so
wsrep_cluster_name = "blog_cluster"
wsrep_cluster_address = "gcomm://10.0.0.1,10.0.0.2,10.0.0.3"
wsrep_node_address = "10.0.0.1"
wsrep_node_name = "ch-01"
wsrep_sst_method = mariabackup
wsrep_sst_auth = "sst:xxx"
binlog_format = ROW
default_storage_engine = InnoDB
innodb_autoinc_lock_mode = 2
bind_address = 0.0.0.0
```
Bootstrap del primer nodo (solo la **primera vez**):
```bash
galera_new_cluster
```
Y en el resto de nodos, arranque normal:
```bash
systemctl start mariadb
```
Verificar estado del cluster:
```sql
SHOW STATUS LIKE 'wsrep_cluster_size';
SHOW STATUS LIKE 'wsrep_cluster_status';
SHOW STATUS LIKE 'wsrep_ready';
SHOW STATUS LIKE 'wsrep_local_state_comment';
```
Señales de salud:
- `wsrep_cluster_size` = número de nodos esperado.
- `wsrep_cluster_status` = `Primary`.
- `wsrep_ready` = `ON`.
- `wsrep_local_state_comment` = `Synced`.
### Consideraciones de Galera
- **Latencia de red** entre nodos: Galera hace un *consensus* por transacción. Si los nodos están en DCs distantes, cada commit paga ese RTT.
- **Write conflicts**: si dos nodos modifican la misma fila a la vez, uno recibe error 1213 (deadlock). La app debe reintentar.
- **Tablas sin PK**: no se replican bien. **Todas las tablas necesitan PK.** InnoDB ya lo recomienda igualmente.
- **DDL**: Galera soporta dos métodos de aplicación, Total Order Isolation (TOI, bloquea el cluster) y Rolling Schema Upgrade (RSU, nodo por nodo). TOI es el default y más seguro.
- **Escalado de escritura**: Galera NO escala escrituras (todas las escrituras tienen que aplicarse en todos los nodos). Lo que te da es HA y lecturas distribuidas.
Para escalar escrituras necesitas sharding, que en MariaDB se hace o con Spider o con lógica de aplicación.
## Backups
### mariabackup
La herramienta moderna, derivada de Percona XtraBackup. Backup físico en caliente, sin bloquear escrituras. Soporta Galera.
Instalación:
```bash
sudo apt install -y mariadb-backup
```
Backup:
```bash
mariabackup --backup --target-dir=/backups/$(date +%Y%m%d-%H%M) \
--user=bkp --password=xxx
```
Backup incremental (requiere un full previo):
```bash
mariabackup --backup \
--target-dir=/backups/inc-$(date +%Y%m%d-%H%M) \
--incremental-basedir=/backups/full-20260518 \
--user=bkp --password=xxx
```
Restore:
```bash
# Preparar (aplica log)
mariabackup --prepare --target-dir=/backups/full-20260518
# Copiar al datadir (con el servicio parado y datadir vacío)
systemctl stop mariadb
rm -rf /var/lib/mysql/*
mariabackup --copy-back --target-dir=/backups/full-20260518
chown -R mysql:mysql /var/lib/mysql
systemctl start mariadb
```
Para restore a un instante concreto, aplicas los binlogs con `mysqlbinlog`:
```bash
mysqlbinlog --start-position=... --stop-datetime='2026-05-18 12:34:56' \
/var/log/mysql/mariadb-bin.00* | mariadb
```
### mysqldump (lógico)
Para migraciones y respaldos pequeños:
```bash
mariadb-dump --single-transaction --routines --triggers --events \
--databases blog > blog.sql
```
`--single-transaction` hace el dump dentro de una transacción REPEATABLE READ, evitando bloqueos. Para tablas no-InnoDB (MyISAM, Aria), esto no sirve y hay que usar `--lock-tables`.
### Estrategia de backup sensata
- **Full** diario (mariabackup).
- **Incremental** cada hora o cada 6 h.
- **Binlog** copiado a almacenamiento externo en tiempo real (puedes hacer `mysqlbinlog --read-from-remote-server --raw ...` en un script con systemd timer).
- **Retención**: 7–30 días según compliance.
- **Almacenamiento**: S3, GCS o equivalente. Nunca en la misma máquina.
- **Restore probado** mensualmente. Un backup no probado es una suposición.
## Monitorización
Métricas imprescindibles:
- **QPS / TPS**.
- **Tasa de errores SQL** (1213 deadlocks, 1205 lock wait timeouts).
- **Buffer pool hit ratio**: objetivo > 99%.
- **Replication lag** (`Seconds_Behind_Master`).
- **Threads_connected** vs `max_connections`.
- **Disk I/O y espacio** en datadir y en `pg_wal`-equivalente (`/var/log/mysql`).
- **Galera**: `wsrep_cluster_size`, `wsrep_flow_control_paused`, `wsrep_local_recv_queue_avg`.
Herramientas:
- **Prometheus + mysqld_exporter + Grafana**. Dashboards listos en grafana.com. Mismo stack que el post [Prometheus y Grafana para servicios pequeños](/post/prometheus-y-grafana-para-servicios-pequenos).
- **Percona PMM**: plataforma completa de monitorización, open source, muy visual. Mi elección si quieres lo mejor listo de serie.
- **pt-stalk / pt-summary** (Percona Toolkit): diagnóstico puntual.
### Alertas mínimas
- Replication lag > 30 s.
- Cluster size distinto al esperado (Galera).
- Deadlocks nuevos > 0 en 5 min (según tu tolerancia).
- Disco > 80%.
- Conexiones usadas > 80% del máximo.
- Buffer pool hit ratio < 99% durante 10 min.
- Slow queries nuevas en el top.
## Seguridad en producción
Mínimo aceptable:
1. **TLS** obligatorio en conexiones externas:
```ini
[mariadb]
ssl_cert = /etc/mysql/server-cert.pem
ssl_key = /etc/mysql/server-key.pem
ssl_ca = /etc/mysql/ca.pem
require_secure_transport = ON
```
2. **scram-sha-256 / ed25519** para autenticación moderna (en vez de `mysql_native_password`):
```sql
CREATE USER 'app'@'10.%.%.%'
IDENTIFIED VIA ed25519 USING PASSWORD('xxx');
```
3. **Usuarios con permisos mínimos**. La app nunca como `root`.
4. **Red privada**: MariaDB nunca expuesto a internet.
5. **Audit log** con el plugin `server_audit`:
```ini
plugin_load_add = server_audit
server_audit_logging = ON
server_audit_events = CONNECT,QUERY_DDL,QUERY_DCL
```
6. **Validación de contraseñas** con `simple_password_check`.
7. **Actualizaciones regulares** de minor.
8. **Roles** (ver [entrega I](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos)) para separar permisos.
## Upgrades
- **Minor** (11.4.x → 11.4.y): simple. Parar, actualizar paquete, arrancar, `mariadb-upgrade` por si hay ajustes de metadata.
- **Major** (10.11 → 11.4): varias rutas:
- **In-place**: parar, reinstalar paquete de la versión nueva, arrancar, `mariadb-upgrade`.
- **Réplica nueva**: levantar una réplica en la versión nueva, promocionar, retirar la antigua.
- **mysqldump**: migración lógica, lenta pero segura.
En cluster Galera, los upgrades se hacen **nodo a nodo**, en rolling. Siempre que la versión nueva sea compatible con la del cluster durante el proceso (consulta la guía oficial para cada salto).
## Checklist de producción
- [ ] Backups full + incremental automatizados a almacenamiento externo.
- [ ] Restore probado en entorno limpio.
- [ ] Replicación configurada con GTID; al menos una réplica.
- [ ] Semi-sync o Galera activo si el nivel de durabilidad lo exige.
- [ ] Failover documentado y probado.
- [ ] Connection pooler (ProxySQL / MaxScale / equivalentes).
- [ ] Monitorización con las métricas de arriba + alertas.
- [ ] TLS y autenticación robusta.
- [ ] `innodb_buffer_pool_size` ajustado a la RAM real (ver [entrega IV](/post/mariadb-desde-cero-iv-indices-explain-y-tuning)).
- [ ] Slow query log activado y revisado periódicamente.
- [ ] Logs centralizados.
- [ ] Binlog con expiración razonable y copiado fuera.
- [ ] Plan de upgrades con ventanas definidas.
- [ ] Red privada, sin MariaDB expuesto a internet.
## Cierre de la serie
Fin del recorrido. En cinco posts hemos pasado de no haber tocado MariaDB a tenerlo desplegado en producción con replicación y backups serios.
Una observación sobre el ecosistema: MariaDB tiene una filosofía muy pragmática. Integra lo que funciona (Galera, mariabackup, ColumnStore) en el producto base y mantiene una compatibilidad alta con MySQL para que no te quedes atrás. A cambio, te pide más trabajo manual que un PostgreSQL con Patroni o que un servicio gestionado. Es un trade-off razonable si tu equipo valora el control.
Serie completa:
- [I: instalación y primeros pasos](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos)
- [II: storage engines, tipos y restricciones](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones)
- [III: consultas, CTEs y window functions](/post/mariadb-desde-cero-iii-consultas-ctes-y-window-functions)
- [IV: índices, EXPLAIN y tuning](/post/mariadb-desde-cero-iv-indices-explain-y-tuning)
- V: replicación, Galera y producción *(estás aquí)*
Y ampliando horizonte de bases de datos:
- [PostgreSQL desde cero a pro](/search?tag=postgresql-desde-cero): la opción por defecto para la mayoría de aplicaciones.
- [ClickHouse desde cero a pro](/search?tag=clickhouse-desde-cero): cuando lo tuyo es analítica a escala.
- [MySQL y MariaDB, MariaDB y MySQL](/post/mysql-y-mariadb-mariadb-y-mysql): la comparativa honesta entre los dos *forks*.
Si tienes dudas sobre montar Galera, elegir entre async y Galera, o depurar un bloqueo recurrente, mis DMs siguen abiertos.
---
# MariaDB desde cero (IV): índices, EXPLAIN y tuning
URL: https://javiervalencia.net/post/mariadb-desde-cero-iv-indices-explain-y-tuning
*Cuarta entrega de la serie **[MariaDB desde cero a pro](/search?tag=mariadb-desde-cero)**. Tiempo de lectura estimado: 13 minutos.*
Ya tienes tablas bien tipadas ([II](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones)) y sabes exprimir el SQL moderno ([III](/post/mariadb-desde-cero-iii-consultas-ctes-y-window-functions)). Antes o después, algo irá lento. Este post es sobre cómo averiguar por qué y qué hacer al respecto.
La filosofía es la misma que en la [entrega IV de PostgreSQL](/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento): medir antes de optimizar, entender el plan antes de tocar nada, y conocer los cuatro o cinco parámetros que de verdad mueven la aguja.
## InnoDB: tabla = índice clustered
En InnoDB (que es lo que usarás casi siempre), **los datos de la tabla se almacenan físicamente en un árbol B+ ordenado por la clave primaria**. A esto se le llama *clustering key* o *clustered index*.
Consecuencias:
- Las consultas por PK (o rango de PK) son baratísimas.
- Los índices secundarios almacenan el valor de la PK como "puntero" a la fila. Si la PK es ancha, todos los índices lo son.
- Insertar con PK aleatoria (UUID v4) fragmenta el árbol. Insertar con PK secuencial (AUTO_INCREMENT) es óptimo.
Regla práctica: **PK numérica auto-incremental corta**. Si necesitas UUID, guárdalo como columna secundaria con su propio índice único, o usa UUID v7 (ordenado por tiempo).
## Tipos de índices
### B-tree secundario
El estándar. Sirve para igualdad, rango y orden.
```sql
CREATE INDEX ix_posts_author ON posts(author_id);
CREATE INDEX ix_posts_published ON posts(published_at);
```
### Índices compuestos
El orden importa. El principio es el mismo que en cualquier SGBD:
```sql
-- Bueno para WHERE author_id = ? AND published_at > ?
CREATE INDEX ix_posts_author_pub ON posts(author_id, published_at);
```
Cubre:
- `WHERE author_id = ?`
- `WHERE author_id = ? AND published_at > ?`
- `WHERE author_id = ? ORDER BY published_at`
No cubre `WHERE published_at > ?` por sí solo.
### Índices sobre columnas virtuales
Como vimos en la [entrega II](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones), el patrón idiomático para indexar expresiones es crear una columna virtual y un índice sobre ella:
```sql
ALTER TABLE users
ADD COLUMN email_lower VARCHAR(320) AS (LOWER(email)) VIRTUAL,
ADD INDEX ix_users_email_lower (email_lower);
-- Usa el índice
SELECT * FROM users WHERE LOWER(email) = 'a@b.com';
```
### Covering (includes en el compuesto)
MariaDB no tiene `INCLUDE` como PostgreSQL. El equivalente es meter las columnas necesarias dentro del propio índice:
```sql
CREATE INDEX ix_posts_slug_covering
ON posts(slug, title, published_at);
```
Si la consulta pide solo `slug`, `title` y `published_at`, el motor responde desde el índice, sin tocar la tabla (*index-only scan*).
### Fulltext
```sql
CREATE FULLTEXT INDEX ft_posts_body ON posts(title, body);
SELECT id, title,
MATCH(title, body) AGAINST('mariadb performance' IN NATURAL LANGUAGE MODE) AS score
FROM posts
WHERE MATCH(title, body) AGAINST('mariadb performance' IN NATURAL LANGUAGE MODE)
ORDER BY score DESC
LIMIT 20;
```
Suficiente para buscadores internos de blogs o catálogos medianos. Para búsquedas sofisticadas, Elasticsearch, Typesense o Meilisearch.
### Spatial
Para tipos geométricos `POINT`, `POLYGON`, etc.:
```sql
CREATE TABLE places (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200),
location POINT NOT NULL,
SPATIAL INDEX sp_places (location)
) ENGINE=InnoDB;
```
Consultas tipo "¿qué hay en este bounding box?" se resuelven vía índice R-tree.
## EXPLAIN
El plan del optimizador:
```sql
EXPLAIN SELECT * FROM posts WHERE author_id = 42;
```
Salida tabular con columnas clave:
- `type`: tipo de acceso. De mejor a peor: `const`, `eq_ref`, `ref`, `range`, `index`, `ALL` (full table scan).
- `key`: índice usado. `NULL` significa que no se usa.
- `rows`: filas estimadas que se leerán.
- `Extra`: información adicional. `Using index` (solo índice), `Using where` (filtro post-scan), `Using temporary` (temp table), `Using filesort` (ordenación en disco/memoria).
### EXPLAIN ANALYZE
Desde MariaDB 10.1. Ejecuta la consulta y muestra tiempos reales:
```sql
EXPLAIN ANALYZE
SELECT p.title, a.name
FROM posts p
JOIN authors a ON p.author_id = a.id
WHERE p.status = 'published';
```
Incluye:
- `r_rows`: filas reales leídas por loop.
- `r_total_time_ms`: tiempo real acumulado.
Cuando `rows` (estimado) y `r_rows` (real) están muy lejos, el optimizador está decidiendo mal. Suele ser que hay que lanzar `ANALYZE TABLE` o ajustar estadísticas.
### FORMAT=JSON
Vista mucho más detallada:
```sql
EXPLAIN FORMAT=JSON SELECT ...;
```
Incluye `cost_info` de cada paso, condiciones empujadas, uso real de índices. Para planes complicados es donde encuentras la respuesta.
## Estadísticas del optimizer
MariaDB usa dos fuentes de estadísticas:
- **InnoDB persistent stats** (por defecto): estadísticas guardadas en tablas de `mysql`. Se calculan con `ANALYZE TABLE` y de forma automática.
- **Engine-independent statistics**: controladas por `use_stat_tables`. Más detalladas, pero menos usadas.
Para recalcular estadísticas:
```sql
ANALYZE TABLE posts;
```
Para tablas muy activas, puedes configurar que se recalculen al cambiar cierto porcentaje:
```sql
ALTER TABLE posts
STATS_AUTO_RECALC = 1,
STATS_SAMPLE_PAGES = 100;
```
Más páginas = más precisión = más coste. 20 (default) es poco para tablas grandes; 100-200 suele dar mucha mejor calidad.
## Slow query log
La herramienta más útil para encontrar las consultas problemáticas:
```ini
[mariadb]
slow_query_log = 1
long_query_time = 0.5
slow_query_log_file = /var/log/mysql/slow.log
log_queries_not_using_indexes = 1
log_slow_admin_statements = 1
```
Para agregarlo:
```bash
mariadb-dumpslow -s t /var/log/mysql/slow.log | head -30
```
Ordena las consultas por tiempo total (`-s t`), tiempo medio (`-s at`), número de llamadas (`-s c`). Es la primera parada cuando algo va mal en producción.
## PERFORMANCE_SCHEMA y sys
`performance_schema` es el esquema de estadísticas detalladas. Desde MariaDB 10.5 hay un helper `sys` con vistas cómodas:
```sql
-- Top consultas por tiempo total
SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT/1e12 AS total_s,
AVG_TIMER_WAIT/1e9 AS avg_ms, SUM_ROWS_EXAMINED
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
-- Tablas con más filas leídas
SELECT OBJECT_SCHEMA, OBJECT_NAME, COUNT_READ, SUM_TIMER_READ/1e12 AS total_s
FROM performance_schema.table_io_waits_summary_by_table
WHERE OBJECT_SCHEMA NOT IN ('mysql','performance_schema','information_schema')
ORDER BY SUM_TIMER_READ DESC
LIMIT 20;
```
Si activas `performance_schema` (normalmente viene on), tienes un radar permanente sin necesidad de extensiones externas.
## Parámetros de servidor que más se notan
La mayoría viven en `/etc/mysql/mariadb.conf.d/50-server.cnf` (Debian/Ubuntu).
### InnoDB
```ini
innodb_buffer_pool_size = 12G # ~70% de la RAM en servidor dedicado
innodb_buffer_pool_instances = 8 # paralelismo de pool
innodb_log_file_size = 1G # redo log. Grande = menos checkpoints
innodb_flush_log_at_trx_commit = 1 # 1 = durable, 2 = cada segundo, 0 = peligroso
innodb_flush_method = O_DIRECT # evita double-buffering en Linux
innodb_io_capacity = 2000 # IOPS en SSD. 200 en HDD
innodb_io_capacity_max = 4000
innodb_read_io_threads = 8
innodb_write_io_threads = 8
innodb_thread_concurrency = 0 # 0 = sin límite
```
El más importante con diferencia: `innodb_buffer_pool_size`. Todo lo demás afecta mucho menos.
### Conexiones
```ini
max_connections = 500
thread_cache_size = 100
table_open_cache = 4000
table_definition_cache = 2000
```
Muchas conexiones simultáneas consumen RAM (cada una reserva buffers). Como en PostgreSQL, pon un pool delante (**ProxySQL**, **HAProxy** con MaxScale, **PgBouncer**-like para MySQL).
### Memoria por operación
```ini
sort_buffer_size = 4M
join_buffer_size = 4M
read_buffer_size = 1M
read_rnd_buffer_size = 1M
tmp_table_size = 64M
max_heap_table_size = 64M
```
**Atención**: estos buffers se reservan por cliente y por operación. Un valor alto puede reventar la RAM si tienes muchas conexiones simultáneas.
### Logs
```ini
log_error = /var/log/mysql/error.log
log_warnings = 2
general_log = 0 # NO activar en producción
log_output = FILE
binlog_format = ROW # para replicación moderna
```
## Receta de depuración
Cuando una consulta va lenta:
1. `EXPLAIN ANALYZE` y compara `rows` con `r_rows`.
2. Si están descuadrados: `ANALYZE TABLE` para refrescar estadísticas. Si persiste, sube `STATS_SAMPLE_PAGES`.
3. Si ves `type: ALL` sobre tabla grande: falta un índice adecuado.
4. Si ves `Using filesort`: o creas un índice con el orden, o subes `sort_buffer_size`, o reduces el número de filas antes de ordenar.
5. Si ves `Using temporary`: revisa `GROUP BY`/`DISTINCT` sobre columnas sin índice.
6. Si el JOIN es lento: valida que las columnas del `ON` están indexadas **en ambas tablas** y con el mismo tipo/charset.
7. Si `InnoDB_buffer_pool_reads` / `InnoDB_buffer_pool_read_requests` es alto (>1%): el buffer pool es pequeño, hay que crecer.
## Bloqueos y deadlocks
InnoDB detecta deadlocks automáticamente y aborta una transacción con error 1213. Para ver el último:
```sql
SHOW ENGINE INNODB STATUS\G
```
La sección `LATEST DETECTED DEADLOCK` muestra las dos transacciones implicadas, las queries y los locks.
Para reducir deadlocks:
- Accede a las filas siempre en el mismo orden.
- Transacciones cortas. Muy cortas.
- Nada de esperas largas (llamadas HTTP, jobs pesados) dentro de una transacción abierta.
- Si haces "bloquea y procesa", usa `SELECT ... FOR UPDATE SKIP LOCKED` (soportado desde 10.6).
## Online DDL
Casi todos los `ALTER TABLE` se hacen en línea sin bloquear escrituras, pero hay excepciones. Comprueba el algoritmo:
```sql
ALTER TABLE posts
ADD COLUMN reading_time INT,
ALGORITHM=INSTANT;
ALTER TABLE posts
ADD INDEX ix_posts_slug (slug),
ALGORITHM=INPLACE, LOCK=NONE;
```
Opciones:
- `ALGORITHM=INSTANT`: cambios metadata-only (añadir columna sin default específico). Inmediato.
- `ALGORITHM=INPLACE`: modifica la tabla en su sitio. Bloquea escrituras solo al principio y al final.
- `ALGORITHM=COPY`: copia toda la tabla. Lento y bloquea.
- `LOCK=NONE`: intenta no bloquear escrituras.
Para tablas enormes, herramientas como **pt-online-schema-change** (Percona Toolkit) o **gh-ost** (GitHub) son la solución histórica. En MariaDB moderno, `ALGORITHM=INPLACE, LOCK=NONE` resuelve la mayoría de casos.
## Connection pooling
Como en todas partes: pon un pool delante. En el ecosistema MariaDB/MySQL:
- **ProxySQL**: proxy completo, con routing, caché, reescritura de queries. Mi elección por defecto.
- **MaxScale** (MariaDB Corporation): proxy con HA, routing, filtros. Bajo licencia BSL.
- **HAProxy**: L4 puro. Suficiente para balanceo simple.
- **Librería con pool interno**: la mayoría de ORMs ya traen pooling básico, útil en apps de un solo proceso.
## Por dónde seguir
- **[V: replicación, Galera y producción](/post/mariadb-desde-cero-v-replicacion-galera-y-produccion)** — último tramo: operar MariaDB en serio.
Repasando:
- [I: instalación y primeros pasos](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos)
- [II: storage engines, tipos y restricciones](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones)
- [III: consultas, CTEs y window functions](/post/mariadb-desde-cero-iii-consultas-ctes-y-window-functions)
---
# MariaDB desde cero (III): consultas, CTEs y window functions
URL: https://javiervalencia.net/post/mariadb-desde-cero-iii-consultas-ctes-y-window-functions
*Tercera entrega de la serie **[MariaDB desde cero a pro](/search?tag=mariadb-desde-cero)**. Tiempo de lectura estimado: 12 minutos.*
En la [entrega I](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos) instalamos MariaDB y en la [II](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones) diseñamos un esquema con tipos y restricciones serias. Ahora explotamos el lenguaje.
Un mito muy extendido es que MariaDB (y MySQL) son "SQL básico". No es cierto desde hace años. MariaDB 10.2+ tiene CTEs, window functions, `CHECK`, JSON. MariaDB 10.3+ añade secuencias estilo Oracle, análisis temporal. Si llevas años sin tocarlo, te vas a llevar sorpresas agradables.
Si vienes de la [serie PostgreSQL](/search?tag=postgresql-desde-cero), muchos patrones te resultarán familiares. Las diferencias están en los detalles.
## CTEs
Since 10.2. Sintaxis estándar `WITH ... AS`:
```sql
WITH paid_invoices AS (
SELECT * FROM invoices WHERE status = 'paid'
),
monthly AS (
SELECT
DATE_FORMAT(paid_at, '%Y-%m') AS month,
SUM(amount) AS total
FROM paid_invoices
GROUP BY 1
)
SELECT
month,
total,
total - LAG(total) OVER (ORDER BY month) AS diff
FROM monthly
ORDER BY month;
```
Ventajas idénticas a las de PostgreSQL: legibilidad, reutilización y separación de lógica.
### CTEs recursivas
```sql
WITH RECURSIVE tree AS (
SELECT id, parent_id, name, 1 AS depth
FROM categories
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.parent_id, c.name, t.depth + 1
FROM categories c
JOIN tree t ON c.parent_id = t.id
)
SELECT * FROM tree ORDER BY depth, name;
```
Misma forma canónica. Útil para árboles, grafos acíclicos, generación de secuencias, etc.
Ejemplo práctico: generar filas de 0 a 99 sin tabla auxiliar:
```sql
WITH RECURSIVE numbers AS (
SELECT 0 AS n
UNION ALL
SELECT n + 1 FROM numbers WHERE n < 99
)
SELECT * FROM numbers;
```
## Window functions
Desde MariaDB 10.2. Sintaxis estándar:
```sql
función() OVER (PARTITION BY ... ORDER BY ... ROWS/RANGE ...)
```
### Ranking
```sql
SELECT
department,
employee,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rk,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS drk
FROM employees;
```
Diferencia entre las tres:
- `ROW_NUMBER()`: números únicos (1, 2, 3, 4).
- `RANK()`: empates comparten puesto y se saltan números (1, 2, 2, 4).
- `DENSE_RANK()`: empates comparten puesto sin saltar (1, 2, 2, 3).
Top N por grupo:
```sql
SELECT *
FROM (
SELECT
category_id, product_id, views,
ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY views DESC) AS rn
FROM products
) ranked
WHERE rn <= 3;
```
### Agregaciones como ventana
```sql
SELECT
order_id,
customer_id,
total,
SUM(total) OVER (PARTITION BY customer_id ORDER BY created_at
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
FROM orders;
```
### lag, lead, first_value
```sql
SELECT
day,
value,
LAG(value, 1) OVER (ORDER BY day) AS prev_day,
LEAD(value, 1) OVER (ORDER BY day) AS next_day,
FIRST_VALUE(value) OVER (ORDER BY day) AS first_val,
value - LAG(value, 1) OVER (ORDER BY day) AS diff
FROM metrics;
```
## UPSERT con ON DUPLICATE KEY UPDATE
La forma histórica y específica de MariaDB/MySQL:
```sql
INSERT INTO page_views (path, day, views)
VALUES ('/home', CURDATE(), 1)
ON DUPLICATE KEY UPDATE
views = views + VALUES(views);
```
Claves clave (valga la redundancia):
- La detección del conflicto depende de **cualquier** restricción única (PK o UNIQUE). No puedes nombrarla como en `ON CONFLICT` de PostgreSQL.
- `VALUES(col)` se refiere al valor que se intentaba insertar. Desde MariaDB 10.3.3 puedes usar alias más claros (`INSERT ... AS new ON DUPLICATE KEY UPDATE x = new.x`).
Desde MariaDB 10.5, **también** hay soporte limitado para `INSERT ... RETURNING` e `INSERT IGNORE`:
```sql
INSERT IGNORE INTO page_views (path, day, views)
VALUES ('/home', CURDATE(), 1);
```
`IGNORE` silencia errores de clave duplicada (y otros). Úsalo con conocimiento: esconde errores reales si no tienes cuidado.
## REPLACE
MariaDB tiene un `REPLACE INTO` que borra la fila existente y la vuelve a insertar si hay conflicto de clave única. **Evítalo**: rompe foreign keys con `ON DELETE CASCADE` y genera WAL extra. Casi siempre `ON DUPLICATE KEY UPDATE` es lo que querías.
## Agregaciones condicionales
No hay `FILTER` como en PostgreSQL. El patrón clásico es `SUM`/`COUNT` con `CASE`:
```sql
SELECT
department,
COUNT(*) AS total,
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) AS active,
AVG(CASE WHEN status = 'active' THEN salary END) AS avg_active_salary
FROM employees
GROUP BY department;
```
Alternativa idiomática cuando solo cuentas condiciones booleanas:
```sql
SELECT
department,
COUNT(*) AS total,
SUM(status = 'active') AS active, -- booleano = 0/1
SUM(hired_at > NOW() - INTERVAL 1 YEAR) AS new
FROM employees
GROUP BY department;
```
MariaDB evalúa comparaciones como `0` o `1`, lo que permite sumarlas directamente. Es conciso y rápido.
## UPDATE y DELETE con JOIN
Una feature útil que no está en SQL estándar:
```sql
-- Update basado en un JOIN
UPDATE posts p
JOIN authors a ON p.author_id = a.id
SET p.title = UPPER(p.title)
WHERE a.status = 'premium';
-- Delete con JOIN
DELETE p
FROM posts p
JOIN expired_users u ON p.author_id = u.id;
```
Perfectamente soportado y a menudo más claro que una subquery correlacionada.
## Multi-table INSERT SELECT
Insert masivo desde otra tabla, con transformación:
```sql
INSERT INTO posts_archive (id, title, archived_at)
SELECT id, title, NOW()
FROM posts
WHERE status = 'archived'
AND archived_at IS NULL;
```
Se ejecuta en una transacción y es mucho más rápido que un bucle de inserts individuales.
## GROUP_CONCAT
Una función muy útil para convertir filas en strings agrupados:
```sql
SELECT
author_id,
GROUP_CONCAT(title ORDER BY published_at DESC SEPARATOR ' | ') AS titles
FROM posts
GROUP BY author_id;
```
`GROUP_CONCAT` tiene un límite en `group_concat_max_len` (1024 bytes por defecto). Si manipulas mucho texto, súbelo:
```sql
SET SESSION group_concat_max_len = 1000000;
```
Para "arrays" verdaderos en una columna, usa JSON: `JSON_ARRAYAGG(title)` devuelve un JSON array.
## Funciones JSON
Ya vimos `JSON_VALID`, `JSON_VALUE`, `JSON_EXTRACT` en la [entrega II](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones). Otras útiles:
```sql
-- Construir objetos/arrays
SELECT JSON_OBJECT('name', name, 'email', email) FROM authors;
SELECT JSON_ARRAY('a', 'b', 'c');
-- Agregar en consulta
SELECT author_id, JSON_ARRAYAGG(id) AS post_ids
FROM posts
GROUP BY author_id;
-- Modificar
UPDATE events
SET data = JSON_SET(data, '$.processed', TRUE)
WHERE id = 42;
UPDATE events
SET data = JSON_REMOVE(data, '$.tmp')
WHERE id = 42;
-- Merge
SELECT JSON_MERGE_PATCH(data1, data2) FROM ...;
```
Ruta JSON: `'$.user.email'`, `'$.items[0]'`, `'$.items[*].price'`.
`JSON_MERGE_PATCH` (RFC 7396) es el comportamiento moderno de merge: recursivo, sustituye arrays.
## Sequences (estilo Oracle/PostgreSQL)
Desde 10.3. Alternativa a `AUTO_INCREMENT`:
```sql
CREATE SEQUENCE seq_posts_id
START WITH 1
INCREMENT BY 1
NOCACHE;
CREATE TABLE posts (
id BIGINT NOT NULL DEFAULT NEXTVAL(seq_posts_id),
...
);
SELECT NEXTVAL(seq_posts_id);
SELECT LASTVAL(seq_posts_id);
SELECT PREVIOUS VALUE FOR seq_posts_id;
```
Ventajas sobre AUTO_INCREMENT:
- Puedes obtener el siguiente valor antes de insertar.
- Soportan `MINVALUE`, `MAXVALUE`, `CACHE`, `CYCLE`.
- Encajan mejor con código SQL portable.
Desventaja: menos conocido en el ecosistema MariaDB, los ORMs a menudo no los soportan de serie.
## System-versioned tables (temporal)
Desde 10.3. Mantén histórico automáticamente:
```sql
CREATE TABLE products (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price DECIMAL(12,2) NOT NULL
) WITH SYSTEM VERSIONING;
-- Consultar "tal y como era el 1 de enero"
SELECT * FROM products FOR SYSTEM_TIME AS OF TIMESTAMP '2026-01-01 00:00:00';
-- Rango de tiempo
SELECT * FROM products FOR SYSTEM_TIME BETWEEN
TIMESTAMP '2026-01-01' AND TIMESTAMP '2026-02-01';
```
Cada `UPDATE` y `DELETE` guarda la versión anterior automáticamente. Útil para auditoría sin montar triggers custom.
Ten en cuenta el tamaño: todas las versiones se acumulan. Puedes particionar por `ROW_END` para poder borrar histórico antiguo.
## Funciones de fecha útiles
```sql
NOW(), CURRENT_TIMESTAMP()
CURDATE(), CURRENT_DATE()
CURTIME()
DATE_ADD(ts, INTERVAL 7 DAY)
DATE_SUB(ts, INTERVAL 30 MINUTE)
DATEDIFF(d1, d2)
TIMESTAMPDIFF(HOUR, t1, t2)
DATE_FORMAT(ts, '%Y-%m-%d %H:%i:%s')
EXTRACT(YEAR FROM ts)
YEAR(ts), MONTH(ts), DAY(ts), HOUR(ts)
LAST_DAY(ts) -- último día del mes
WEEK(ts, 1) -- modo ISO (lunes)
DAYOFWEEK(ts) -- 1 = domingo
```
## Un ejemplo realista
Funnel de conversión, versión MariaDB:
```sql
WITH sessions AS (
SELECT
session_id,
user_id,
MIN(CASE WHEN event = 'visit' THEN created_at END) AS visited_at,
MIN(CASE WHEN event = 'signup' THEN created_at END) AS signed_up_at,
MIN(CASE WHEN event = 'purchase' THEN created_at END) AS purchased_at
FROM events
WHERE created_at >= NOW() - INTERVAL 30 DAY
GROUP BY session_id, user_id
)
SELECT
COUNT(*) AS visits,
SUM(signed_up_at IS NOT NULL) AS signups,
SUM(purchased_at IS NOT NULL) AS purchases,
ROUND(100 * SUM(signed_up_at IS NOT NULL) / NULLIF(COUNT(*), 0), 2) AS signup_rate,
ROUND(100 * SUM(purchased_at IS NOT NULL) / NULLIF(SUM(signed_up_at IS NOT NULL), 0), 2) AS conversion_rate
FROM sessions
WHERE visited_at IS NOT NULL;
```
CTE + booleanos como enteros + `NULLIF` para evitar divisiones por cero. Todo lo que haría falta en la vida real.
## Por dónde seguir
- **[IV: índices, EXPLAIN y tuning](/post/mariadb-desde-cero-iv-indices-explain-y-tuning)** — rendimiento y diagnóstico.
- **[V: replicación, Galera y producción](/post/mariadb-desde-cero-v-replicacion-galera-y-produccion)** — cierre.
Si aterrizas ahora:
- [I: instalación y primeros pasos](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos)
- [II: storage engines, tipos y restricciones](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones)
---
# MariaDB desde cero (II): storage engines, tipos y restricciones
URL: https://javiervalencia.net/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones
*Segunda entrega de la serie **[MariaDB desde cero a pro](/search?tag=mariadb-desde-cero)**. Tiempo de lectura estimado: 11 minutos.*
En la [entrega anterior](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos) instalamos MariaDB, creamos una base de datos y vimos los primeros comandos. Ahora vamos a lo que distingue MariaDB del resto: el concepto de **storage engine**, el sistema de tipos y las restricciones disponibles.
## Storage engines
A diferencia de PostgreSQL (un único motor) o ClickHouse (varios engines pero una familia unificada), en MariaDB cada **tabla** puede usar un motor distinto. Cada engine tiene pros y contras claros. Elegir bien es parte del diseño.
### InnoDB
El engine por defecto desde hace mucho. Transaccional, ACID, soporta foreign keys, MVCC, row-level locking.
Para casi todas las tablas de una aplicación normal, la respuesta es **InnoDB**. No tienes que pensárselo.
```sql
CREATE TABLE ... ENGINE=InnoDB;
```
Es el único engine que soporta foreign keys respetadas. Los demás pueden declararlas pero no las hacen cumplir.
### Aria
Sucesor de MyISAM. Crash-safe, no transaccional. Usado internamente para tablas de sistema y buen candidato para tablas **temporales grandes** que no necesitan transacciones ni foreign keys.
```sql
CREATE TABLE tmp_report ... ENGINE=Aria;
```
### MyRocks
Basado en RocksDB de Facebook. Optimizado para **escritura intensiva** y **alta compresión**. Ocupa notablemente menos que InnoDB en datasets grandes.
```sql
CREATE TABLE events ... ENGINE=RocksDB;
```
Casos donde MyRocks brilla:
- Logs, eventos, *time series* con mucho INSERT y poco UPDATE.
- Datasets que no caben en RAM pero sí en SSD.
- Sistemas donde el tamaño en disco importa.
No lo uses como engine principal de una app típica. Es una herramienta especializada.
### ColumnStore
Motor columnar al estilo ClickHouse, integrado en MariaDB. Pensado para analítica. Si tu carga es 80% OLTP y 20% reporting, ColumnStore puede evitar montar otro sistema.
Dicho esto: si la carga analítica es seria, sigue siendo mejor un [ClickHouse dedicado](/search?tag=clickhouse-desde-cero) al lado. ColumnStore encaja como solución intermedia.
### Spider
Engine de *sharding* federado: tablas lógicas que viven en varios servidores MariaDB backend. Poderoso, pero complejo. Solo si realmente entiendes lo que estás montando.
### MEMORY, CSV, ARCHIVE, BLACKHOLE
Engines de nicho:
- **MEMORY**: tabla en RAM. Rápida, se pierde al reiniciar.
- **CSV**: fichero CSV presentado como tabla. Útil para ETL.
- **ARCHIVE**: insert-only, muy comprimido.
- **BLACKHOLE**: descarta escrituras pero loggea en binlog. Truco clásico para construir replicación custom.
### Qué engine elegir
- **OLTP normal** → InnoDB.
- **Logs/métricas con mucho write** → MyRocks.
- **Analytics en la misma instancia** → ColumnStore.
- **Tablas temporales** → Aria.
- **Resto** → InnoDB.
## El sistema de tipos
### Enteros
```sql
TINYINT -- 1 byte, -128..127 (o 0..255 con UNSIGNED)
SMALLINT -- 2 bytes
MEDIUMINT -- 3 bytes (peculiar de MySQL/MariaDB)
INT -- 4 bytes
BIGINT -- 8 bytes
```
El modificador `UNSIGNED` dobla el rango positivo. Para claves primarias autoincrementales suele ir bien `BIGINT UNSIGNED`.
Precisión: `INT(11)` **no** significa "entero de 11 dígitos". Significa "ancho de display si usas ZEROFILL". Es confuso y MariaDB 10.6+ lo marca como deprecado. Escribe `INT` a secas.
### Decimales y floats
```sql
DECIMAL(P, S) -- precisión exacta. DECIMAL(12,4) para dinero
FLOAT -- 4 bytes
DOUBLE -- 8 bytes
```
Mismo mantra que en las otras bases: **dinero = `DECIMAL`**, siempre.
### Strings
```sql
CHAR(n) -- fija, hasta 255
VARCHAR(n) -- variable, hasta 65.535 bytes por fila total
TINYTEXT -- hasta 255 bytes
TEXT -- 64 KB
MEDIUMTEXT -- 16 MB
LONGTEXT -- 4 GB
BINARY, VARBINARY, BLOBs -- equivalentes binarios
```
Para texto en español con emoji, usa `VARCHAR(n)` con `CHARACTER SET utf8mb4`. Recuerda: en `utf8mb4`, un carácter puede ocupar hasta 4 bytes, así que `VARCHAR(255)` podría consumir 1020 bytes.
### Fechas y tiempos
```sql
DATE -- 3 bytes
TIME -- 3 bytes
DATETIME -- 5..8 bytes, sin zona
TIMESTAMP -- 4..7 bytes, convertido a UTC internamente
YEAR -- 1 byte
```
Diferencia importante entre `DATETIME` y `TIMESTAMP`:
- `TIMESTAMP`: rango 1970..2038 (históricamente), se almacena como UTC y se convierte a la zona horaria de la sesión.
- `DATETIME`: rango 1000..9999, se almacena tal cual, sin conversión.
Para timestamps con soporte horario-aware, **usa `TIMESTAMP`** en MariaDB, con cuidado del horizonte 2038 si tu software perdura (en MariaDB moderno se amplía). Para fechas de eventos histórico-arbitrarios, `DATETIME`.
### JSON
Desde MariaDB 10.2 existe `JSON`. Internamente se almacena como `LONGTEXT` con validación. Funciones:
```sql
CREATE TABLE events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
type VARCHAR(40) NOT NULL,
data JSON NOT NULL,
CHECK (JSON_VALID(data))
) ENGINE=InnoDB;
INSERT INTO events (type, data)
VALUES ('signup', JSON_OBJECT('email', 'a@b.com', 'plan', 'free'));
-- Acceso
SELECT JSON_VALUE(data, '$.email') AS email FROM events;
SELECT JSON_EXTRACT(data, '$.plan') FROM events;
-- Búsqueda
SELECT * FROM events WHERE JSON_VALUE(data, '$.plan') = 'free';
-- Columna virtual indexable
ALTER TABLE events
ADD COLUMN email VARCHAR(320) AS (JSON_VALUE(data, '$.email')) VIRTUAL,
ADD INDEX ix_events_email (email);
```
No es tan potente como `JSONB` de PostgreSQL (no hay índices GIN nativos), pero el patrón "columna virtual + índice" cubre los casos más habituales.
### ENUM y SET
```sql
status ENUM('draft', 'published', 'archived') NOT NULL DEFAULT 'draft'
```
ENUM es rápido y ocupa poco. Contras: añadir valores requiere `ALTER TABLE`, lo que en tablas grandes puede ser costoso. Alternativa: `VARCHAR` con `CHECK`.
`SET` permite guardar combinaciones de valores permitidos en una sola columna. Poca gente lo usa; cuando aparece un caso, suele ser más limpio con una tabla relacional.
## Restricciones
### NOT NULL y DEFAULT
Igual que en cualquier SQL. Úsalas sin miedo: una columna no nula es una invariante que el resto del sistema puede asumir.
### PRIMARY KEY y AUTO_INCREMENT
```sql
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id)
```
Alternativa moderna con `SEQUENCE` (desde 10.3):
```sql
CREATE SEQUENCE seq_posts_id START WITH 1 INCREMENT BY 1;
CREATE TABLE posts (
id BIGINT NOT NULL DEFAULT NEXTVAL(seq_posts_id),
...
);
```
La ventaja de las secuencias es que cruzan transacciones sin reservar rangos y son más parecidas a lo que hay en Oracle o PostgreSQL.
### UNIQUE
```sql
UNIQUE KEY ux_authors_email (email)
UNIQUE (tenant_id, slug)
```
Al contrario que PostgreSQL, MariaDB no tiene índices únicos **parciales**. Truco habitual: columna virtual + índice:
```sql
ALTER TABLE users
ADD COLUMN email_active VARCHAR(320) AS (IF(deleted_at IS NULL, email, NULL)) VIRTUAL,
ADD UNIQUE KEY ux_users_email_active (email_active);
```
### CHECK
Desde MariaDB 10.2 los `CHECK` se aplican de verdad (en versiones previas solo se parseaban y se ignoraban).
```sql
CREATE TABLE products (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price DECIMAL(12,2) NOT NULL CHECK (price >= 0),
sku VARCHAR(30) NOT NULL CHECK (sku REGEXP '^[A-Z0-9-]{4,20}$')
) ENGINE=InnoDB;
```
### Foreign keys
Solo InnoDB las respeta. Sintaxis:
```sql
CONSTRAINT fk_posts_author
FOREIGN KEY (author_id) REFERENCES authors(id)
ON DELETE RESTRICT ON UPDATE CASCADE
```
Opciones: `RESTRICT`, `CASCADE`, `SET NULL`, `NO ACTION`, `SET DEFAULT`.
Peculiaridad histórica: MariaDB **no** hace *deferred constraint checking*. Las FK se validan en el mismo statement. Esto evita ciertos patrones de inserción por lotes que sí funcionan en PostgreSQL (con `DEFERRABLE INITIALLY DEFERRED`).
## Generated columns
Columnas calculadas a partir de otras. Dos modos:
- `VIRTUAL`: se calculan al leer. No ocupan disco.
- `STORED` (o `PERSISTENT` en MariaDB): se calculan al escribir y se guardan.
```sql
CREATE TABLE invoices (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
subtotal DECIMAL(12,2) NOT NULL,
vat_rate DECIMAL(4,2) NOT NULL,
total DECIMAL(12,2) AS (subtotal * (1 + vat_rate)) VIRTUAL,
issued_at DATE NOT NULL,
year_month VARCHAR(7) AS (DATE_FORMAT(issued_at, '%Y-%m')) PERSISTENT,
INDEX ix_invoices_ym (year_month)
) ENGINE=InnoDB;
```
Puedes **indexar** columnas virtuales. Es el patrón estándar para acelerar consultas sobre expresiones (equivalente a los *expression indexes* de PostgreSQL, vía columna intermedia).
## Claves compuestas y orden de columnas
En InnoDB, la **PRIMARY KEY es el clustering key**: los datos de la tabla se almacenan físicamente ordenados por ella. Esto tiene implicaciones enormes:
- Claves primarias anchas **inflan todos los índices secundarios** (que incluyen la PK).
- Claves primarias auto-incrementales secuenciales son baratas (inserts al final).
- Claves primarias aleatorias (UUID v4) fragmentan las páginas y degradan escritura.
Regla práctica en InnoDB:
- PK numérica auto-incremental corta (BIGINT o INT).
- Si necesitas UUIDs externos, considera UUID v7 (ordenados por tiempo) o guarda el UUID como columna secundaria.
Orden en claves compuestas: **igualdad antes que rango**, mismo principio que el resto de sistemas.
## Un esquema completo
```sql
CREATE DATABASE IF NOT EXISTS blog
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE blog;
CREATE TABLE authors (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(200) NOT NULL,
email VARCHAR(320) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY ux_authors_email (email),
CONSTRAINT ck_authors_email_format CHECK (email LIKE '%@%')
) ENGINE=InnoDB;
CREATE TABLE posts (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
author_id BIGINT UNSIGNED NOT NULL,
title VARCHAR(300) NOT NULL,
slug VARCHAR(300) NOT NULL,
body MEDIUMTEXT NOT NULL,
status ENUM('draft','published','archived') NOT NULL DEFAULT 'draft',
metadata JSON NULL,
published_at DATETIME NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY ux_posts_slug (slug),
KEY ix_posts_author_published (author_id, published_at),
CONSTRAINT fk_posts_author FOREIGN KEY (author_id) REFERENCES authors(id) ON DELETE RESTRICT,
CONSTRAINT ck_posts_published CHECK (status != 'published' OR published_at IS NOT NULL),
CONSTRAINT ck_posts_metadata CHECK (metadata IS NULL OR JSON_VALID(metadata))
) ENGINE=InnoDB;
CREATE TABLE tags (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(80) NOT NULL,
UNIQUE KEY ux_tags_name (name)
) ENGINE=InnoDB;
CREATE TABLE post_tags (
post_id BIGINT UNSIGNED NOT NULL,
tag_id BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (post_id, tag_id),
KEY ix_post_tags_tag (tag_id),
CONSTRAINT fk_pt_post FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
CONSTRAINT fk_pt_tag FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
) ENGINE=InnoDB;
```
Detalles:
- `ENGINE=InnoDB` explícito en todas.
- `CHECK` sobre estado y `published_at`: si el post está publicado, debe tener fecha.
- `CHECK` sobre el JSON para que sea válido.
- `ON DELETE RESTRICT` en la FK de autor (no se puede borrar un autor con posts).
- `ON DELETE CASCADE` en la tabla de unión.
- Índice secundario en `post_tags(tag_id)` para recorrer "tag → posts".
## Por dónde seguir
- **[III: consultas, CTEs y window functions](/post/mariadb-desde-cero-iii-consultas-ctes-y-window-functions)** — el SQL moderno.
- **[IV: índices, EXPLAIN y tuning](/post/mariadb-desde-cero-iv-indices-explain-y-tuning)** — rendimiento y diagnóstico.
- **[V: replicación, Galera y producción](/post/mariadb-desde-cero-v-replicacion-galera-y-produccion)** — cierre de la serie.
Si llegaste tarde: [I: instalación y primeros pasos](/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos).
---
# El secreto ibérico en El Gallo: el corte que se hace o se quema en treinta segundos
URL: https://javiervalencia.net/post/pena-flamenca-el-gallo-secreto-iberico
Tercera entrega de la serie sobre la cocina de Paco Flores en la peña flamenca El Gallo, en Las Lagunas de Mijas. Después de la [presentación general](/post/pena-flamenca-el-gallo-mijas-presentacion-de-la-serie) y de la [alcachofa con brandada de bacalao](/post/pena-flamenca-el-gallo-alcachofa-con-brandada-de-bacalao), toca un cambio de registro completo. De la verdura más austera al corte más goloso del cerdo: **el secreto ibérico**.
Este es uno de esos platos que en la pizarra ponen muchos sitios y que casi nadie hace bien. La distancia entre un secreto memorable y un secreto correcto cabe en treinta segundos de plancha. Por eso este plato, aparentemente fácil, es uno de los exámenes más serios para una cocina de barra.
## Qué es el secreto ibérico
Empecemos por lo básico, porque hay confusión. El secreto ibérico es un corte de la zona del **cabecero del lomo**, entre la paleta y el lomo del cerdo, escondido (de ahí el nombre) bajo la grasa subcutánea. No es solomillo ni es presa. Es un músculo plano, alargado, con una **infiltración de grasa intramuscular** muy alta cuando viene de un cerdo ibérico de bellota. Esa grasa, blanca, brillante, casi traslúcida, es lo que distingue el corte. En boca, se deshace y deja sabor; cocinada bien, suelta jugo sin que la carne quede grasienta.
El cerdo ibérico no es lo mismo que el cerdo blanco. La raza, la dieta (bellota, recebo, cebo) y los meses de montanera cambian completamente la materia prima. Un secreto de cerdo ibérico de bellota es un producto serio; un secreto de cerdo blanco etiquetado como "secreto" es otra cosa, más cercana al pluma o al magro tradicional, y no admite la misma cocina. Cuando un sitio tiene **secreto ibérico** en pizarra y lo respeta, está jugando en una liga concreta. Cuando hablo de este plato en El Gallo, hablo de eso.
## El reto de la plancha
Aquí está el quid. El secreto ibérico se cocina, casi siempre, en plancha o en parrilla. Y aquí es donde la mayoría de los sitios fallan. Hay tres errores típicos:
1. **Plancha tibia.** La plancha tiene que estar **muy caliente**, no caliente. Si está tibia, la grasa se derrite antes de que se forme la costra exterior, la carne suelta agua, queda gris por fuera y triste por dentro. Es el error más común y el que más mata el plato.
2. **Cocción excesiva.** El secreto ibérico, por la grasa intramuscular, **pide poco fuego**. Punto medio, tirando a poco. Pasarse de fuego es secarlo, perder la jugosidad, convertir el corte premium en una suela de zapato. La diferencia entre el punto y el carbón está en treinta segundos por lado, literal.
3. **Sal mal puesta.** La sal pone antes en exceso saca el jugo del secreto antes de la plancha y resta. La sal puesta al final, en escama, sobre la carne caliente, es la que respeta el corte.
El secreto, por su grosor (uno o dos centímetros, no más), se cocina en muy poco tiempo. Plancha al rojo, un par de minutos por cada lado a lo sumo, dependiendo del grosor exacto, y a la mesa. **Si tarda más de cinco minutos, algo va mal.**
## Cómo lo borda Paco
He visto a Paco trabajar el secreto en plancha en barra (la barra está abajo y la cocina queda detrás), y la rutina es de las más limpias que he observado. Plancha que ya está al punto cuando entra la comanda, no a calentar; corte ya temperado, no recién sacado de cámara; sal al final; reposo brevísimo antes de plato. Sin más.
Lo que diferencia su versión es la **disciplina del tiempo**. No hace nada extraordinario. No flambea, no marina, no envuelve en bacon. **Hace lo correcto, en el momento correcto.** Esa es exactamente la cocina que quiero comer cuando pago por un secreto ibérico: no quiero al chef demostrando técnica, quiero al chef respetando el producto.
El resultado en plato: costra dorada (no negra), carne con un punto rosado al centro, jugo suficiente para que el cuchillo se enganche un instante en cada corte, grasa veteada que brilla a la luz. Sal en escama por encima. Y nada más. Ningún emplatado florido, ningún ramito de hierbas, ningún sirope decorativo. El plato pide silencio.
## Acompañamientos: lo que pedir, lo que no
Aquí es donde mucha gente se equivoca al armar la mesa. El secreto ibérico no admite cualquier guarnición.
**Lo que funciona:**
- **Pimiento verde frito** (italiano, asado en sartén con un poco de aceite y sal). Aporta verde, dulzor, contraste de textura, y no compite con el corte.
- **Patata pobre** (patata cortada fina, hecha despacio en aceite, no frita rápido en sartén industrial). Si el sitio la hace bien, es la guarnición clásica que mejor encaja.
- **Verde sencillo** (tomate aliñado, lechuga, rúcula). Lo que la mesa española lleva poniendo cien años junto a la carne. No falla.
**Lo que rompe el plato:**
- **Patatas fritas industriales** con sal de freidora y aceite reusado. Aplastan el sabor del secreto y dejan el paladar saturado de aceite. Si el sitio solo tiene patatas industriales, mejor pedir la carne sola.
- **Salsas pesadas** (cualquier reducción dulce, salsas de queso, salsas con mucho fondo). Tapan el corte. El secreto ibérico se basta solo; añadirle salsa es desconfiar de la materia prima.
- **Mostaza, mayonesa**. No son ofensivos por sí mismos, pero no son el sitio. Quien come secreto en barra quiere el sabor del cerdo ibérico, no el de la salsa.
En El Gallo, conviene pedir **secreto a la plancha + pimiento verde** o **secreto + patata pobre**, no las dos. Si la mesa es de tres o cuatro, una ración del corte y dos guarniciones para compartir es la composición razonable.
## Vino: dónde sí entra el tinto
A diferencia de la alcachofa con brandada, el secreto ibérico **sí admite tinto**, y de hecho lo pide. La grasa intramuscular del corte casa con tintos que tengan **acidez para limpiar paladar** y **fruta sin barrica excesiva** para acompañar. Tres opciones que recomendaría, en orden de cercanía geográfica al sitio:
1. **Tinto joven de Sierras de Málaga** (DO Sierras de Málaga). Garnacha, tempranillo o syrah de la zona, sin barrica o con barrica corta. Apuesta local; juega bien con la cocina del sur.
2. **Garnacha de Aragón joven**. Garnachas frescas, frutosas, sin sobreextracción. La acidez de la garnacha bien hecha es perfecta para la grasa del ibérico.
3. **Mencía joven del Bierzo o de Ribeira Sacra**. Más arriesgado, más mineral. Para mesas que quieran salir del binomio "carne = tinto con barrica" y descubrir otra cosa.
**Lo que evitaría**: tintos con paso largo por barrica (Reserva, Gran Reserva). Pueden funcionar en otras carnes, pero aquí hacen ruido. La grasa del ibérico no necesita madera, necesita acidez y fruta limpia.
Si el día es de mucho calor, una **caña de cerveza fría** acompaña perfectamente. La acidez del lúpulo en una pilsner correcta funciona como sustituto del vino. Pero no aciertes con una IPA: demasiado lúpulo agresivo, pelearía con el corte.
## Cómo pedirlo
Tres apuntes:
1. **Pídelo en ración**, no en tapa. La media ración deja a casi todo el mundo con ganas de más; la ración entera permite repartir bien y disfrutar del corte sin tener que correr.
2. **Al punto, tirando a poco**. Si te preguntan, esa es la respuesta. Punto significa rosado al centro, no rojo crudo y no gris. Si no preguntan, sale así por defecto en una cocina seria.
3. **Llévalo de los segundos**, después de algo más ligero. El secreto ibérico es plato denso. Si lo metes al principio, vas a saciarte temprano y no vas a poder con el resto. Después de la alcachofa con brandada, por ejemplo, encaja perfecto.
## Para terminar
El secreto ibérico es uno de esos platos donde la **simplicidad es el examen**. No hay donde esconderse: si la materia prima es buena y la plancha está al punto, sale memorable; si falla cualquiera de los dos, sale mediocre. En El Gallo, sale memorable. Y eso, en una zona donde el secreto ibérico se ofrece en demasiados sitios sin demasiado respeto, es razón suficiente para volver.
La próxima entrega de la serie: el solomillo al Pedro Ximénez, en detalle. La salsa, la bodega, la reducción y por qué un PX de supermercado nunca dará el mismo resultado que un PX de bodega seria.
---
# MariaDB desde cero (I): instalación y primeros pasos
URL: https://javiervalencia.net/post/mariadb-desde-cero-i-instalacion-y-primeros-pasos
*Primera entrega de la serie **[MariaDB desde cero a pro](/search?tag=mariadb-desde-cero)**. Tiempo de lectura estimado: 9 minutos.*
Arranco la tercera serie sobre bases de datos: cinco posts sobre MariaDB, la implementación libre del linaje MySQL que hoy tiene roadmap propio y sigue siendo una opción muy sensata para muchos proyectos.
Si dudas entre MariaDB y MySQL como tecnologías, el post [MySQL y MariaDB, MariaDB y MySQL](/post/mysql-y-mariadb-mariadb-y-mysql) es un buen punto de partida. Aquí doy por supuesto que ya te has decidido por MariaDB o que estás en un entorno donde lo tienes delante.
Esta serie va en paralelo a las de [PostgreSQL](/search?tag=postgresql-desde-cero) y [ClickHouse](/search?tag=clickhouse-desde-cero). Cuando una idea aparece también en las otras, la enlazo.
## Por qué MariaDB hoy
MariaDB nació como *fork* de MySQL cuando Oracle compró Sun en 2009. Durante años fue un *drop-in replacement*, pero desde MySQL 8 y MariaDB 10.5 se han ido separando lo suficiente como para que conviene saber en cuál estás.
Razones por las que MariaDB sigue siendo una buena elección:
- **GPL pura** y desarrollo 100% abierto (MariaDB Foundation).
- Varios **storage engines** integrados: InnoDB, Aria, MyRocks, ColumnStore, Spider.
- **Compatibilidad Oracle parcial**: modo PL/SQL y sintaxis de secuencias desde 10.3.
- Funciones modernas de SQL: CTEs, window functions, análisis estadístico.
- **Galera Cluster** integrado para HA síncrona multi-master.
- `mariabackup`, compatible con snapshots en caliente.
Para un sistema transaccional de tamaño mediano, MariaDB es una elección sin drama.
## Instalación
### Debian/Ubuntu
El repositorio oficial con paquetes actualizados:
```bash
curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash
sudo apt update
sudo apt install -y mariadb-server mariadb-client
sudo systemctl enable --now mariadb
sudo mariadb-secure-installation
```
`mariadb-secure-installation` hace lo típico: establece contraseña de root, elimina usuarios anónimos, desactiva login remoto de root, etc. Úsalo.
### Docker
```bash
docker run -d \
--name mdb \
-e MARIADB_ROOT_PASSWORD=secret \
-p 3306:3306 \
-v mdbdata:/var/lib/mysql \
mariadb:11.4
```
Puerto 3306, el clásico. El directorio de datos mantiene el nombre histórico `/var/lib/mysql/` por compatibilidad.
## El cliente `mariadb`
Antiguamente `mysql`, hoy `mariadb` (el binario `mysql` sigue siendo un alias en la mayoría de paquetes). Acepta prácticamente las mismas opciones y meta-comandos.
```bash
mariadb -u root -p
```
Comandos útiles dentro del cliente:
```
\h -- ayuda
SHOW DATABASES;
USE nombre;
SHOW TABLES;
SHOW CREATE TABLE tabla\G
DESCRIBE tabla;
SHOW PROCESSLIST;
STATUS;
\e -- abre $EDITOR
\q -- salir
```
`\G` en lugar de `;` muestra la salida en formato vertical, muy útil para filas anchas. Equivale al `\x` de psql.
Mi `~/.my.cnf` básico (modo cliente):
```ini
[client]
user=javier
password=...
host=localhost
[mariadb]
prompt="\\u@\\h [\\d]> "
auto-rehash
```
`prompt` muestra usuario, host y base de datos activa. `auto-rehash` habilita autocompletado de tablas y columnas.
## Autenticación por socket y usuarios
En Debian/Ubuntu modernos, el usuario `root` de MariaDB se autentica por **Unix socket** por defecto: si eres root del sistema, entras sin contraseña con `sudo mariadb`. Es seguro y cómodo.
Para crear usuarios:
```sql
-- Usuario local con contraseña
CREATE USER 'app'@'localhost' IDENTIFIED BY 'xxx';
-- Usuario accesible desde una red interna
CREATE USER 'app'@'10.0.%.%' IDENTIFIED BY 'xxx';
-- Permisos completos sobre una BD
GRANT ALL PRIVILEGES ON blog.* TO 'app'@'localhost';
-- Permisos granulares, como debería ser en producción
GRANT SELECT, INSERT, UPDATE, DELETE ON blog.* TO 'app'@'localhost';
FLUSH PRIVILEGES;
```
Un detalle clave que confunde al que viene de PostgreSQL: en MariaDB, el usuario es **`'usuario'@'host'`**. El mismo nombre desde distintos hosts puede tener distintos permisos. Es peculiar pero muy útil.
Para roles (similar a PostgreSQL):
```sql
CREATE ROLE app_read;
GRANT SELECT ON blog.* TO app_read;
GRANT app_read TO 'reporter'@'localhost';
SET DEFAULT ROLE app_read FOR 'reporter'@'localhost';
```
## Tu primera base de datos
```sql
CREATE DATABASE blog
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE blog;
CREATE TABLE authors (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(200) NOT NULL,
email VARCHAR(320) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY ux_authors_email (email)
) ENGINE=InnoDB;
CREATE TABLE posts (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
author_id BIGINT UNSIGNED NOT NULL,
title VARCHAR(300) NOT NULL,
slug VARCHAR(300) NOT NULL,
body MEDIUMTEXT NOT NULL,
published_at DATETIME NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY ux_posts_slug (slug),
KEY ix_posts_author_published (author_id, published_at),
CONSTRAINT fk_posts_author FOREIGN KEY (author_id) REFERENCES authors(id)
) ENGINE=InnoDB;
```
Piezas que ya introducen el sabor de MariaDB:
- **`utf8mb4`**: el charset que quieres. `utf8` en MySQL/MariaDB es una versión limitada (3 bytes) por razones históricas. Con `utf8mb4` soportas emoji y todo Unicode.
- **`ENGINE=InnoDB`**: el storage engine transaccional. Lo desgranamos en la [entrega II](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones).
- **`UNSIGNED`**: enteros sin signo. Ahorras la mitad del rango negativo cuando no lo necesitas.
- **`AUTO_INCREMENT`**: la secuencia clásica. Desde 10.3 también están las `SEQUENCE` al estilo Oracle/PostgreSQL.
- **`ON UPDATE CURRENT_TIMESTAMP`**: magia de MariaDB que mantiene `updated_at` solo.
## Primeros INSERT y SELECT
```sql
INSERT INTO authors (name, email)
VALUES ('Javier', 'javier@example.com');
SELECT LAST_INSERT_ID();
-- Devuelve el id generado
INSERT INTO posts (author_id, title, slug, body, published_at)
VALUES
(1, 'Hola mundo', 'hola-mundo', 'Primer post', NOW()),
(1, 'En borrador', 'borrador', 'Aún no publicado', NULL);
SELECT id, title, published_at IS NOT NULL AS publicado
FROM posts
ORDER BY created_at DESC;
```
A diferencia de PostgreSQL, MariaDB no tiene `RETURNING` universal (aunque sí soporta un subset limitado: `INSERT ... RETURNING` desde 10.5). El patrón habitual para INSERT es `LAST_INSERT_ID()`.
## Transacciones
MariaDB con InnoDB soporta transacciones ACID:
```sql
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- o ROLLBACK;
```
Savepoints:
```sql
START TRANSACTION;
INSERT INTO ... ;
SAVEPOINT sp1;
UPDATE ... ; -- podría fallar
ROLLBACK TO sp1;
COMMIT;
```
Nivel de aislamiento por defecto: `REPEATABLE READ` (distinto a PostgreSQL, que va con `READ COMMITTED`). Para una explicación larga de las diferencias, merece un post entero.
## Configuración: los ficheros que importan
En Debian/Ubuntu, la configuración está dispersa en varios ficheros que se unen por el método include:
- `/etc/mysql/mariadb.cnf` — fichero raíz.
- `/etc/mysql/conf.d/` — configuración compartida con clientes.
- `/etc/mysql/mariadb.conf.d/50-server.cnf` — la config del servidor.
Los ajustes más habituales van en `50-server.cnf` o en tus propios `.cnf` en `mariadb.conf.d/`.
Parámetros para mirar cualquier valor:
```sql
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW STATUS LIKE 'Threads_connected';
```
Y para ver estado global:
```sql
SHOW GLOBAL STATUS;
```
## Log slow query
Lo primero que activo en un servidor que dudo:
```sql
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- segundos
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
```
Y en producción, persiste en el `.cnf`:
```ini
[mariadb]
slow_query_log = 1
long_query_time = 1
slow_query_log_file = /var/log/mysql/slow.log
log_queries_not_using_indexes = 1
```
Con `mariadb-dumpslow` (antes `mysqldumpslow`) agregas el fichero para ver las consultas lentas agrupadas. Es la primera parada cuando algo va mal. Volveremos a ello en la [entrega IV](/post/mariadb-desde-cero-iv-indices-explain-y-tuning).
## Character set y collation
Por omisión, usa `utf8mb4`. En versiones modernas de MariaDB ya es el default del servidor, pero conviene verificar:
```sql
SHOW VARIABLES LIKE 'character_set_%';
SHOW VARIABLES LIKE 'collation_%';
```
Si heredas un sistema viejo con `latin1` o `utf8` (3 bytes), considéralo deuda técnica y planea una migración. Es un dolor de cabeza recurrente.
## Por dónde seguir
- **[II: storage engines, tipos y restricciones](/post/mariadb-desde-cero-ii-storage-engines-tipos-y-restricciones)** — InnoDB, Aria, MyRocks, tipos numéricos, JSON, foreign keys.
- **[III: consultas, CTEs y window functions](/post/mariadb-desde-cero-iii-consultas-ctes-y-window-functions)** — el SQL moderno que MariaDB soporta desde 10.2.
- **[IV: índices, EXPLAIN y tuning](/post/mariadb-desde-cero-iv-indices-explain-y-tuning)** — rendimiento en serio.
- **[V: replicación, Galera y producción](/post/mariadb-desde-cero-v-replicacion-galera-y-produccion)** — replicación async, cluster síncrono y operación.
Como lectura complementaria: [MySQL y MariaDB, MariaDB y MySQL](/post/mysql-y-mariadb-mariadb-y-mysql) para tener clara la comparativa con MySQL.
---
# Alcachofa con brandada de bacalao en El Gallo: cuando un vegetal alcanza la categoría celestial
URL: https://javiervalencia.net/post/pena-flamenca-el-gallo-alcachofa-con-brandada-de-bacalao
Esta es la segunda entrega de la serie sobre la cocina de Paco Flores en la peña flamenca El Gallo, en Las Lagunas de Mijas. La primera fue la [presentación general](/post/pena-flamenca-el-gallo-mijas-presentacion-de-la-serie), un recorrido por la pizarra del día. Esta es la primera que entra en un plato concreto, y no es casualidad que sea este. Si tuviera que elegir un único plato de la pizarra para enseñarle a alguien lo que es capaz de hacer Paco con producto humilde, no dudaría: **la alcachofa con brandada de bacalao**.
Es, sin medias tintas, **un plato ejecutado a la perfección**. De los que justifican el viaje, de los que se piden a la primera y se vuelven a pedir antes de irse. Y eso requiere explicación, porque a primera vista hay poco con lo que deslumbrar: una verdura considerada menor, una preparación de origen humilde y un emplatado sin pirotecnia. Lo que pasa entre el primer bocado y el último, sin embargo, es otra historia.
## El reto de la alcachofa
Antes de hablar del plato, conviene hablar de la alcachofa. La alcachofa es un vegetal complicado. No por su sabor, que cuando está bien es uno de los más característicos de la huerta del sur, sino por todo lo que la rodea. Tiene **cinarina**, un compuesto fenólico que distorsiona la percepción de los sabores: vuelve dulce al agua, mete sabores metálicos en cualquier vino mal elegido, ensucia los maridajes. Tiene una textura tramposa: si se cocina poco, queda áspera; si se pasa, se deshace en hilachas. Tiene, además, una mala fama heredada: en demasiados sitios la han servido conservada en aceite barato, fría, mate, sin gracia. Cualquier comensal de los que pisa peñas sabe lo que es pedir alcachofas y arrepentirse.
Por todo eso, cuando un chef pone alcachofa en pizarra, está mojándose. No es un plato de zona segura. Es un plato que mide al cocinero. Si la alcachofa sale floja, lo notas a la primera; si sale bien, también, y la diferencia entre las dos es enorme. Paco la ha puesto en pizarra, en mayo, con la temporada de alcachofa de Tudela y de Benicarló acabándose. La elección no es casual: significa que ha encontrado materia prima a la altura y que confía en su técnica para sostenerla.
## Qué es exactamente este plato
Vamos a la descripción seca, antes de subirme a interpretaciones. El plato consta de:
- **Alcachofa entera**, limpiada hasta el corazón, blanqueada y luego acabada en plancha o en horno. Las hojas exteriores fuera, el tallo pelado y aprovechado, los pétalos abiertos lo justo para que la brandada anide entre ellos.
- **Brandada de bacalao**, la preparación clásica provenzal: bacalao desalado y desmigado, emulsionado con aceite de oliva y, en muchas versiones, una pequeña base de patata cocida que aporta cuerpo. La de Paco va más en la línea cremosa que en la rústica: queda untuosa, casi montada, sin hilos largos de bacalao.
- **Una pizca de algo más**, según el día: a veces un punto de aceite verde, a veces unas láminas finas de algo crujiente por encima. Detalles que cambian sin hacer ruido.
Y ya está. No hay ni emplatado de hojas decorativas, ni espumas, ni gels, ni nada que distraiga. Es un plato corto en elementos y largo en intención. Eso, en cocina, suele ser señal de seguridad.

*Foto: Josef Schlaghecken, [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) vía [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Artischocken_Anbau_in_der_Pfalz-1-Josef_Schlaghecken.jpg).*
## La técnica que distingue
Lo que separa una alcachofa con brandada correcta de la versión que sirve Paco está en cuatro decisiones técnicas que se notan en el plato sin que haga falta explicarlas:
**Limpieza generosa de la alcachofa.** Quitar muchas más hojas exteriores de las que la inercia pediría. La cocina barata deja capas duras "para que rinda más"; la cocina seria sabe que esas capas son fibra y amargor, y que la alcachofa solo merece la pena en su corazón. El resultado es que cada bocado es comestible, sin tener que ir descartando hojas como si fuera una alcachofa cocida casera.
**Cocción doble.** Primero un blanqueado en agua acidulada (con limón o vinagre, para que no se oxide y mantenga el verde). Luego un acabado en plancha bien caliente o en horno fuerte, que aporta el matiz tostado, casi caramelizado, en los bordes. Una sola cocción no daría ese contraste; en una sola cocción, o queda blanda y triste, o queda dura y áspera.
**Brandada montada con disciplina.** El bacalao, perfectamente desalado (esto es trabajo de víspera, no se improvisa), se desmiga y se emulsiona con aceite a temperatura controlada. La textura final tiene que ser **suave pero sabrosa**: capaz de napar la alcachofa sin chorrear, capaz de ceder en boca sin disolverse antes de tiempo, capaz de aportar grasa noble sin pesar. La brandada de Paco está exactamente en ese punto.
**El napado.** Aquí es donde se nota la mano. La brandada no se planta en un montón al lado, ni se mete con manga pastelera en florituras. **Napa la alcachofa a la perfección**: rellena los huecos entre los pétalos abiertos, corona el corazón, baja por los bordes lo justo para que cada cuchara recoja verdura y crema en la misma proporción. Ese gesto, que parece simple, lo dice todo del cocinero. Mucha alcachofa con brandada que he comido por el mundo trae la brandada en un cuenco aparte y te toca a ti reconciliarlas en boca; aquí ya vienen reconciliadas en plato.
## Por qué eleva la alcachofa a categoría celestial
Aquí hay que decir algo que en este blog no se dice a menudo: este plato **eleva un vegetal humilde a categoría celestial**. La alcachofa es una de esas verduras que, mal trabajadas, son un trámite, y que bien trabajadas son una experiencia. La cocina de Paco lleva esta versión hasta el extremo en el que la verdura, sin perder nada de su carácter (su amargor sutil, su densidad fibrosa controlada, su matiz vegetal de fondo), gana una dimensión nueva: pasa de ser materia prima de huerta a ser **vehículo de placer**.
La brandada, suave pero sabrosa, hace de cómplice. No se impone, no roba protagonismo, no compite con la alcachofa por ver quién manda. La acompaña. La envuelve. Le aporta esa grasa salina que la alcachofa, por su naturaleza un poco austera, agradece. El bacalao desalado pone el punto de fondo de mar, lejano pero presente, que termina de redondear el bocado. Y todo junto, sostenido por la temperatura justa del plato, **armoniza el paladar hasta niveles que pocas veces se alcanzan en cocina de barra**: dulzor vegetal, amargor sutil de la alcachofa, salinidad de la brandada, untuosidad del aceite, el contraste del borde tostado contra la cremosidad central. Cinco capas de sensación en una cucharada que cabe en cualquier cuchara sopera.
No es marketing. Es lo que pasa cuando una técnica sólida se aplica con honestidad sobre dos productos buenos. La diferencia entre comer alcachofa con brandada en cualquier sitio y comerla en El Gallo es la diferencia entre ejecutar una receta y firmarla.
## Tres vinos para acompañarlo
Llegamos al maridaje. Y aquí toca avisar: la alcachofa es uno de los vegetales **más difíciles del mundo del vino**. La cinarina, ese compuesto del que hablábamos al principio, distorsiona el paladar y hace que vinos que en otro contexto funcionarían bien aquí salgan metálicos, dulzones falsos, descompensados. No vale cualquier blanco, y tintos casi mejor olvidarlos. Hay que afinar.
Después de probar este plato más de una vez y de jugar con varias copas distintas (a veces solo, a veces en mesa de cuatro, a veces en tapa de barra), estos son los tres vinos que mejor se han comportado, ordenados de más clásico a más arriesgado. Cada uno tiene sentido en distintos formatos: si lo pides en **tapa**, en barra, basta con una copa; si lo pides en **ración**, para compartir, conviene una botella entera.
### 1. Manzanilla en rama (Sanlúcar de Barrameda)
El maridaje canónico de la alcachofa, escriba lo que escriba la sumillería contemporánea. Una manzanilla en rama, recién sacada de la bota, fría pero no congelada, es la respuesta a casi todos los problemas que plantea este plato. Su **salinidad neutraliza la cinarina**: lo que en un albariño o en un verdejo flojo se convertiría en un sabor metálico, en una manzanilla simplemente desaparece. Su sequedad limpia la grasa de la brandada en cada sorbo, dejando el paladar listo para el siguiente bocado.
- **En tapa**: un **chato** o una copa pequeña. Frío. Suficiente para acompañar dos o tres bocados.
- **En ración**: media botella o botella entera, según mesa, servida en cubo con hielo.
Si la peña tiene manzanilla en rama de saca corta, esta es la elección. Si solo hay manzanilla embotellada de hace meses, sigue funcionando, pero pierde parte del juego.
### 2. Verdejo de Rueda (de bodega seria, sin sobreextracción)
El segundo en el podio. Un verdejo bien hecho —no el verdejo industrial, frutoso y aromático en exceso, sino el verdejo de bodega seria que respeta la uva— tiene **notas herbáceas** (hinojo silvestre, monte bajo, anís ligero) que dialogan directamente con la alcachofa. Donde la manzanilla neutraliza, el verdejo conversa: encuentra terreno común con el vegetal y lo empuja sin tapar la brandada.
Hay que afinar la elección, eso sí. Verdejos demasiado modernos, con maceraciones largas y exceso de tiol, son demasiado ruidosos para este plato. Lo que se busca es un verdejo limpio, con acidez seria, fermentado en depósito, sin paso por barrica.
- **En tapa**: una copa fría. Excelente para barra.
- **En ración**: botella entera para mesa de cuatro.
### 3. Blanco seco del marco de Málaga (Sierras de Málaga)
Llamada local. La DO Sierras de Málaga lleva años produciendo blancos secos serios, sobre todo a partir de moscatel vinificado en seco y de uvas locales. Un **moscatel de Alejandría seco**, bien hecho, tiene una nariz aromática (muy distinta a la dulzona del moscatel de postre) y una boca seca y mineral que se comporta sorprendentemente bien con la brandada. Donde el albariño pelearía con la alcachofa, el moscatel seco bien vinificado se planta y aguanta.
Esta es la apuesta de quien quiere acompañar una cocina de Las Lagunas de Mijas con un vino de su mismo entorno geográfico. No es la opción más segura de los tres, pero es la más coherente con el lugar donde se está comiendo.
- **En tapa**: copa pequeña, fresca pero no helada.
- **En ración**: botella entera. Saca su mejor versión cuando hay tiempo de tomarse el plato sin prisa.
### Lo que evitaría
Para cerrar el maridaje y para que sirva de aviso a quien venga: **no pediría tinto** con este plato. Ni de Rioja, ni de Ribera, ni nada con barrica. La alcachofa lo va a romper. Tampoco pediría blancos demasiado afrutados (sauvignon blanc ruidoso, riesling dulce, blancos modernos con mucha barrica): el plato pide austeridad y precisión, no amabilidad. Y, por supuesto, nada de cervezas de fermentación alta o lúpulos agresivos, que también pelearían con la cinarina.
Si el día es de mucho calor y la copa de vino se hace pesada, la alternativa razonable es una **cerveza tipo lager bien fría**. Pero, si se puede, vino.
## Si vas, cómo pedirlo
Tres apuntes prácticos para quien lo pida por primera vez:
1. **Pídelo de los primeros**, no de los últimos. La alcachofa con brandada es plato de paladar limpio. Si llegas a él después de seis platos potentes, no la vas a apreciar igual. En la lógica de mesa de la peña, esto va de entrada o entre los primeros.
2. **Decide el formato según mesa**. En tapa, para uno o dos comensales que quieren probarlo, es perfecto. En ración, para tres o cuatro que quieren entrar en serio, es donde el plato se disfruta como merece. Pregunta al personal por el tamaño: en la pizarra no se especifica, y la diferencia entre tapa y ración es sustancial.
3. **No le pongas pan al principio**. La tentación de mojar pan en la brandada es inmediata. Aguanta los primeros bocados sin pan, para apreciar el plato en limpio. Después, sí, ya puedes mojar lo que quieras.
## Para terminar
La alcachofa con brandada de bacalao es, hoy por hoy, el plato que utilizaría para presentar la cocina de Paco Flores a alguien que entra por primera vez en la peña. No porque sea el más espectacular (otros, como el huevo de gansa, están en otra liga de ambición), sino porque concentra, en un plato sin pretensión visual, todo lo que distingue una cocina hecha con cabeza de una cocina hecha por cumplir: producto bueno, técnica afinada, contención en el emplatado, generosidad en la limpieza, honestidad en el sabor.
Si vas a la peña con dudas y solo te atreves con un plato, este. Si vas con compañía y quieres empezar fuerte, este también. Y si vas a probar la cocina de Paco para decidir si vuelves, ya te lo digo: con este plato, vuelves.
La próxima entrega de la serie: el secreto ibérico, en detalle. Mientras tanto, si te interesa el tema, suscríbete al RSS y te van llegando.
---
# PostgreSQL desde cero (V): replicación, backups y producción
URL: https://javiervalencia.net/post/postgresql-desde-cero-v-replicacion-backups-y-produccion
*Quinta y última entrega de la serie **[PostgreSQL desde cero a pro](/search?tag=postgresql-desde-cero)**. Tiempo de lectura estimado: 14 minutos.*
Último tramo. En las entregas anteriores has aprendido a instalar PostgreSQL ([I](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos)), diseñar esquemas serios ([II](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones)), escribir consultas avanzadas ([III](/post/postgresql-desde-cero-iii-ctes-window-functions-y-consultas-avanzadas)) y diagnosticar rendimiento ([IV](/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento)). Ahora toca lo que separa un PostgreSQL "que funciona" de uno con el que tu jefe puede dormir tranquilo: **replicación**, **backups**, **monitorización** y **HA**.
No voy a cubrir absolutamente todo. Voy a dar la columna vertebral, suficiente para que puedas llevar a producción un clúster pequeño-mediano y saber qué buscar cuando necesites escalar.
## WAL: lo que hay que entender antes de nada
PostgreSQL escribe cada cambio primero en el **Write-Ahead Log** (WAL). Es un log binario secuencial: cualquier modificación se persiste en el WAL **antes** de cambiar los ficheros de datos. Todo lo demás —replicación, backups, point-in-time recovery— se construye sobre esto.
- Los WAL segments viven en `$PGDATA/pg_wal/`.
- Cada segmento es un fichero (16 MB por defecto).
- Se reciclan cuando ya no hacen falta para recovery ni para réplicas.
Si un servidor tiene `archive_mode = on`, cada WAL lleno se copia a un almacenamiento externo vía `archive_command`. Esa copia es la base de los backups físicos y del *point-in-time recovery*.
## Backups
### Tipos de backup
- **Lógicos** (`pg_dump`, `pg_dumpall`): exportan SQL. Portables entre versiones, lentos para datasets grandes, no permiten PITR.
- **Físicos** (`pg_basebackup`, `WAL-G`, `pgBackRest`, `Barman`): copias binarias del directorio de datos + los WAL archivados. Rápidos para restaurar, permiten PITR.
Para producción, **siempre físicos**. `pg_dump` sigue siendo útil para migraciones entre versiones mayores o para snapshots de tablas específicas.
### pg_dump
Lógico, por base de datos:
```bash
pg_dump -Fc -f blog.dump blog
# Formato custom (-Fc): comprimido, permite restore selectivo
pg_restore -d blog_nuevo blog.dump
```
Para una sola tabla:
```bash
pg_dump -t posts -Fc -f posts.dump blog
```
Útil en desarrollo y en migraciones. En producción de un sistema grande, no es tu herramienta principal.
### pg_basebackup
Backup físico básico, desde una réplica o desde el master con `primary`:
```bash
pg_basebackup \
-h primary.internal \
-U replicator \
-D /var/lib/postgresql/17/backup \
-Fp -Xs -P -R
```
- `-Fp`: plain (los ficheros tal cual).
- `-Xs`: streamea WAL durante el backup.
- `-P`: muestra progreso.
- `-R`: escribe `standby.signal` y `primary_conninfo`, preparando el directorio para ser réplica.
Rápido y suficiente para clusters pequeños. Para los serios, la siguiente herramienta.
### WAL-G, pgBackRest, Barman
Las tres herramientas de facto para backups en producción:
- **WAL-G**: rápida, pensada para cloud (S3, GCS, Azure), paraleliza bien. Mi elección por defecto.
- **pgBackRest**: muy completa, excelentes opciones de retención y verificación. Estándar en entornos on-prem.
- **Barman**: veterana, buena integración con Debian.
Configuración típica de WAL-G con S3 (en `postgresql.conf`):
```conf
archive_mode = on
archive_command = 'wal-g wal-push %p'
archive_timeout = 60
```
Y la variable de entorno con la config:
```bash
WALG_S3_PREFIX=s3://backups-pg/prod
AWS_REGION=eu-west-1
WALG_COMPRESSION_METHOD=brotli
```
Para hacer un base backup:
```bash
wal-g backup-push /var/lib/postgresql/17/main
```
Y para listar/restaurar:
```bash
wal-g backup-list
wal-g backup-fetch /var/lib/postgresql/17/main LATEST
```
### Point-in-time recovery
Lo que hace insustituibles los backups físicos: restaurar a un instante exacto.
```conf
# recovery.conf o postgresql.auto.conf
restore_command = 'wal-g wal-fetch %f %p'
recovery_target_time = '2026-05-01 12:34:56 UTC'
recovery_target_action = 'promote'
```
Recuperas el último base backup anterior a esa hora y aplicas los WAL hasta llegar justo al punto pedido. Perfecto para deshacer un DROP TABLE accidental, un UPDATE que olvidó el WHERE, o cualquier error de los que duelen.
**Probá tus backups**. Un backup sin restore probado es placebo. Programa restores periódicos a un servidor staging. Si no has restaurado nunca, no tienes backup.
## Replicación streaming
Una primaria acepta escrituras y envía su WAL a una o más réplicas. Las réplicas aplican el WAL y quedan sincronizadas (con algo de lag).
### Configuración básica en la primaria
En `postgresql.conf`:
```conf
wal_level = replica # o 'logical' si además haces logical replication
max_wal_senders = 10
wal_keep_size = 1GB # o usa replication slots
hot_standby = on
```
Crear el rol de replicación:
```sql
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'xxx';
```
En `pg_hba.conf`:
```
host replication replicator 10.0.0.0/8 scram-sha-256
```
### Configuración de la réplica
1. Parar PostgreSQL en la réplica.
2. Limpiar `$PGDATA`.
3. `pg_basebackup ... -R` desde la primaria.
4. Arrancar. Aparece como standby.
El fichero `standby.signal` marca que el servidor es una réplica; `postgresql.auto.conf` incluye el `primary_conninfo` generado por `-R`.
Verificar:
```sql
-- En la primaria
SELECT client_addr, state, sent_lsn, replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes
FROM pg_stat_replication;
-- En la réplica
SELECT pg_is_in_recovery();
SELECT now() - pg_last_xact_replay_timestamp() AS replication_lag;
```
### Replication slots
Sin replication slot, la primaria puede reciclar un WAL que la réplica aún necesita y romper la replicación. Los slots garantizan que la primaria conserve los WAL necesarios:
```sql
SELECT pg_create_physical_replication_slot('replica_1');
```
En la réplica, `primary_slot_name = 'replica_1'` en `postgresql.conf`.
Cuidado con el revés: si una réplica se cae y el slot queda huérfano, la primaria acumulará WAL indefinidamente hasta llenar el disco. Monitoriza siempre los slots.
### Replicación sincrónica
Por defecto, la replicación es **asíncrona**: la primaria no espera a la réplica. Si quieres que los commits solo confirmen cuando la réplica los ha aplicado:
```conf
synchronous_standby_names = 'ANY 1 (replica1, replica2)'
```
Coste: latencia de escritura aumenta. Ganancia: cero data loss ante caída de la primaria. Para cuentas bancarias, sí. Para analítica, normalmente no.
## Alta disponibilidad y failover
PostgreSQL no tiene failover automático integrado. Necesitas orquestarlo. Opciones:
- **Patroni** + etcd/Consul/ZooKeeper. Es el estándar de facto.
- **repmgr**: más simple, menos potente.
- **Managed services** (RDS, Cloud SQL, Crunchy Bridge): delegas el failover.
Ideas comunes a todas:
- El "leader" es uno, decidido por consenso externo.
- Las réplicas promocionan cuando el leader se cae.
- Un **proxy** delante (HAProxy, PgBouncer con `server_routing`) enruta al leader actual.
Una vez promocionada una réplica, la antigua primaria **no puede reincorporarse sin más**: habría divergido. Herramientas como `pg_rewind` reconcilian automáticamente; Patroni lo hace por ti.
## Logical replication
Distinta a la streaming: en lugar de replicar WAL binario, replica **cambios lógicos** (INSERT/UPDATE/DELETE en tablas concretas). Usa:
- Replicación entre versiones mayores distintas (16 → 17).
- Migración online de un servidor a otro.
- Partir datos por tablas: "estas 5 tablas al servidor A, estas 3 al B".
- Pipelines de CDC (change data capture) a Kafka, Debezium, analíticas.
Setup básico:
```sql
-- En el origen
ALTER SYSTEM SET wal_level = 'logical';
-- reiniciar
CREATE PUBLICATION pub_all FOR ALL TABLES;
-- En el destino
CREATE SUBSCRIPTION sub_all
CONNECTION 'host=origen ...'
PUBLICATION pub_all;
```
Caveats: no replica DDL (solo datos), los slots pueden acumular WAL si el subscriber no está al día, no todas las operaciones se replican (TRUNCATE sí desde 11, DDL no).
## Monitorización mínima
Métricas imprescindibles:
- **Latencia de consultas** (p95, p99).
- **Lag de replicación**: segundos de retraso entre primaria y réplicas.
- **Tasa de commits y rollbacks**.
- **Uso de conexiones** vs `max_connections`.
- **Tasa de cache hit**: `blks_hit / (blks_hit + blks_read)` desde `pg_stat_database`. Objetivo: >99%.
- **Tamaño de `pg_wal/`** y tamaño de WAL archivados pendientes.
- **Autovacuum**: tablas con `n_dead_tup` creciente y `last_autovacuum` antiguo.
- **Disco**: llenado de `$PGDATA` y del volumen de WAL.
Herramientas:
- **Prometheus + `postgres_exporter` + Grafana**. Dashboards listos en grafana.com.
- **pgwatch2**, **pganalyze** (comercial), **pgHero** (simple y bueno).
Si ya montas Prometheus/Grafana, el post [Prometheus y Grafana para servicios pequeños](/post/prometheus-y-grafana-para-servicios-pequenos) aplica igual.
### Alertas mínimas
- `replication_lag_bytes` > 100 MB sostenido 5 minutos.
- `replication_lag_time` > 30 segundos.
- `disk_usage` > 80%.
- `connections_used / max_connections` > 80%.
- `cache_hit_ratio` < 95%.
- `deadlocks` > 0 en última hora.
- `autovacuum_running` con duración extrema.
- WAL archivados pendientes > umbral.
## Seguridad
Lo mínimo en producción:
1. **TLS obligatorio** en conexiones externas (`hostssl` en pg_hba, `ssl = on` en postgresql.conf).
2. **Contraseñas `scram-sha-256`**, nunca `md5` en nuevos despliegues.
3. **Roles sin `SUPERUSER`** para la app (ya lo vimos en la [entrega I](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos)).
4. **Red privada**: PostgreSQL jamás expuesto a internet directamente.
5. **Secret management**: contraseñas en variables de entorno, vaults o equivalentes, no en repos.
6. **Row Level Security** para multitenancy:
```sql
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON posts
USING (tenant_id = current_setting('app.tenant_id')::bigint);
```
7. **Audit log** si el compliance lo exige (extensión `pgaudit`).
## Upgrades
- **Minor** (17.1 → 17.2): parar, instalar paquete, arrancar. No hay cambios de formato en disco.
- **Major** (16 → 17): varias rutas:
- `pg_upgrade`: rápido, in-place, requiere downtime corto.
- Logical replication: sin downtime pero más complejo.
- `pg_dump/pg_restore`: simple pero lento para datos grandes.
La recomendación: probar **siempre** en staging con una copia real antes de tocar producción. Y tener un plan de rollback explícito.
## Checklist rápida de producción
Antes de declarar "en producción", repasa:
- [ ] Backups físicos automatizados a almacenamiento externo.
- [ ] **Restore probado** en entorno limpio.
- [ ] Al menos una réplica con lag monitorizado.
- [ ] Failover documentado y probado (runbook).
- [ ] Connection pooler (PgBouncer u otro).
- [ ] Monitorización con las métricas de arriba + alertas.
- [ ] TLS y autenticación robusta.
- [ ] `postgresql.conf` ajustado a los recursos reales (ver [entrega IV](/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento)).
- [ ] Autovacuum ajustado para tablas calientes.
- [ ] Plan de upgrades (minor y major) con ventanas conocidas.
- [ ] Logs centralizados y con retención.
- [ ] Segurización de red (sin PostgreSQL expuesto a internet).
Si marcas todo, estás muy por encima de la media.
## Cierre de la serie
En cinco posts hemos recorrido PostgreSQL desde instalarlo hasta operarlo con seriedad. Como siempre, el 80% del valor está en las decisiones tempranas: modelo bien pensado, tipos correctos, restricciones que garantizan invariantes, índices donde hacen falta. Lo demás es operación: importante pero secundaria.
Mis dos recomendaciones finales:
1. **Empieza pequeño, instrumenta desde el día 1**. Un PostgreSQL con `pg_stat_statements` y métricas básicas te ahorra sorpresas. Las mejores optimizaciones las hace PostgreSQL por sí solo si tiene estadísticas buenas.
2. **No caigas en la tentación de microservicios-base-de-datos antes de tiempo**. Una PostgreSQL bien diseñada con schemas y RLS aguanta mucho más de lo que la gente cree. El coste de operar varias bases es real.
Serie completa:
- [I: instalación, psql y primeros pasos](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos)
- [II: tipos, restricciones y relaciones](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones)
- [III: CTEs, window functions y consultas avanzadas](/post/postgresql-desde-cero-iii-ctes-window-functions-y-consultas-avanzadas)
- [IV: índices, EXPLAIN y rendimiento](/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento)
- V: replicación, backups y producción *(estás aquí)*
Y si quieres seguir adentrándote en el ecosistema de bases de datos:
- [ClickHouse desde cero a pro](/search?tag=clickhouse-desde-cero): cuando PostgreSQL deja de rendir en analytics.
- [PostgreSQL: 10 consultas que todo desarrollador debería conocer](/post/postgresql-10-consultas-que-todo-desarrollador-deberia-conocer): repaso rápido de las herramientas que más he usado.
Si tienes dudas sobre montar replicación, diseñar un esquema concreto o depurar una consulta lenta, mis DMs siguen abiertos.
---
# La peña flamenca El Gallo, en Las Lagunas de Mijas: presentación de una serie sobre la cocina de Paco Flores
URL: https://javiervalencia.net/post/pena-flamenca-el-gallo-mijas-presentacion-de-la-serie
En la Costa del Sol hay tres tipos de sitios para comer fuera. Sitios para impresionar a los amigos de fuera, donde lo que importa es la vista, el mantel y el ticket que enseñas en el grupo de WhatsApp. Sitios para llevar a un cliente, donde lo que importa es que no falle nada. Y sitios para volver, los más raros, donde lo que importa es lo que pasa dentro del plato. La peña flamenca El Gallo, en Las Lagunas de Mijas, pertenece a la tercera categoría, que es la más valiosa de las tres y la que menos se publicita.
Lo que iba a ser un post se ha convertido en una decisión consciente: voy a escribir una serie. Hay tantas cosas que decir sobre la cocina del chef Paco Flores, sobre el ambiente del local y sobre la pizarra que cambia con la temporada, que pretender resumirlo todo en mil quinientas palabras sería hacerle un flaco favor al sitio. **Esta entrega es la presentación.** A partir de aquí, irán cayendo posts dedicados a platos concretos y a técnicas concretas. Aviso por adelantado: serán posts largos y serán varios. Si lo que buscas son listas de cinco bullets para leer en el ascensor, este blog no es para ti, y esta serie en particular tampoco.
Vamos por partes.
## Qué es una peña flamenca y por qué importa
Para quien no haya pisado nunca una, conviene aclarar primero qué es exactamente una peña flamenca. Una peña no es un tablao para turistas con sangría caliente y bailaora con bata de cola actuando para una mesa de daneses. Una peña flamenca es una **asociación cultural** con socios, normalmente sin ánimo de lucro, organizada alrededor del flamenco como expresión artística. Tienen un pequeño escenario, paredes con fotos firmadas de cantaores que han pasado por allí, un cartel con la programación del año, y casi siempre una taberna que es la que sostiene económicamente al resto.
La taberna es la clave. Sin la taberna, las peñas no podrían pagar el alquiler que permite que los jueves de cante o los viernes de jam sean posibles. Por eso la cocina importa: no es un complemento, es la columna vertebral económica de la institución cultural. Cuando uno entra a una peña flamenca buena, está viendo, sin saberlo, un modelo discreto de financiación cultural privada que lleva décadas funcionando.
La peña El Gallo, en Las Lagunas de Mijas, encaja en esa descripción al milímetro. Local en dos plantas: el escenario arriba —sin mesas, solo el espacio para que cante quien tenga que cantar—, la barra abajo y, fuera, la terraza. Sala formal no hay. Paredes con su iconografía y una taberna que, según he ido descubriendo, le saca a la cocina muchísimo más de lo que pediría el formato. **Eso es lo que ha hecho que escriba este post**, porque las peñas que cuidan la cocina son una minoría dentro de una minoría.
## La pizarra: una declaración de principios

Cuando entras y te acomodan —barra o terraza, sala no hay—, lo primero que hace el camarero es señalarte la pizarra de la pared. La carta no es una carta impresa de cuatro hojas plastificadas. Es una pizarra grande de madera con el listado del día escrito a tiza, en mayúsculas y minúsculas mezcladas, con la letra de quien escribe rápido porque tiene cosas que hacer en cocina. Eso ya dice cosas: lo que hay hoy es lo que hay hoy, mañana puede ser otra cosa, y el chef tiene libertad para cambiar la oferta según producto, según temporada, según ganas.
La pizarra que aparece en la imagen tenía **veintinueve platos**. Veintinueve. Lo suficiente para que cualquier indeciso encuentre algo, lo suficientemente acotada para que cocina pueda dar lo que ofrece sin tirar de congelador. Pero ojo a un detalle que conviene aclarar antes de pedir: en esa pizarra no se distingue lo que es tapa, lo que es media ración y lo que es ración entera. Algunas cosas vienen como tapa, otras como ración, y unas cuantas se pueden pedir tanto en formato pequeño como grande, según se quiera picar o sentarse en serio. **Hay que preguntar al personal**, porque la pizarra no especifica el formato.
Y aquí entra otra cosa importante.
## El personal: el detalle que no debería sorprender pero sorprende
Por insistir en algo que en esta era de aplicaciones para pedir desde la mesa con un código QR se está infravalorando: el personal de sala de la peña flamenca El Gallo es **uno de los más simpáticos y agradables que me he encontrado en mucho tiempo en un sitio de comer**. Y lo digo después de pisar muchos sitios y de haber dejado de volver a unos cuantos por culpa del trato.
No es la simpatía mecánica del camarero entrenado para sonreír a la cámara cada veinte segundos. Es esa cosa más rara y más valiosa que es **estar a gusto en su trabajo**. Te recomiendan sin presionar; te corrigen sin condescendencia (cosas como "eso pidiéndolo en ración entera os quedáis cortos para los cuatro, mejor pedid también los pinchos"); te cuentan qué ha llegado fresco esa mañana; te avisan, sin hacerse los importantes, de que algo se ha agotado. En la mesa de al lado, una pareja preguntó si la rosada era de Mercamálaga o de la lonja local. La respuesta fue clara, sin esquivar la pregunta. Esa transparencia, en una zona donde la trazabilidad del pescado es a veces opaca, se agradece.
Si vas a la peña, **déjate aconsejar**. No es de esos sitios donde te tratan de tonto si pides ayuda. Te tratan como si fueran a comer ellos contigo, y eso es algo que se ha vuelto raro.
## Paco Flores, el chef
Paco Flores es el cocinero. Lo digo así, en seco, porque su cocina no necesita adjetivos publicitarios. Necesita que la pruebes. Y, una vez probada, no necesita explicación.
Cocinar bien con producto bueno y tres comensales en la mesa cómodos es relativamente fácil. Cocinar muy bien con producto bueno y cinco mesas pidiendo platos distintos a la vez, en una pizarra que cambia, con una cocina pequeña detrás de una taberna de peña flamenca, eso es otro nivel. Lo que hace Paco no es la cocina del chef de Instagram, con plato emplatado para foto cenital. Es **cocina honesta, técnica sólida, sabores limpios**, y un punto de personalidad que se nota en pequeños detalles: una salsa que no es exactamente la del libro de texto, una cocción ligeramente más larga, una guarnición inesperada que cuadra con el resto.
Tengo, en la provincia de Málaga y alrededores, varios sitios a los que voy específicamente por el cocinero, no por el local ni por la zona. La peña flamenca El Gallo se ha sumado a esa lista corta. Y eso, después de muchos años comiendo por aquí, no me pasa con frecuencia.
Quien quiera entender la diferencia entre un chef que ejecuta una carta y un chef que **la firma**, este es buen sitio para verlo. Cada plato de la pizarra que viene a continuación está pensado por alguien que sabe exactamente lo que está haciendo y por qué.
## Una vuelta por la pizarra
Aquí entra el corazón de este post: una vuelta plato a plato (o, al menos, a las grandes familias) por la pizarra. No es una crítica detallada de cada plato (eso vendrá en las próximas entregas, con su técnica, su origen y mis notas de cata), sino una presentación general para que se entienda **qué te encuentras al llegar a la pizarra**.
### Pescados y mariscos
La pizarra arranca con el pescado, lo cual ya es una declaración. En una zona donde demasiados sitios tiran de carta común con cinco pescados rebozados anónimos, aquí se mojan: ventresca, rosada, araña, calamar, bacalao, pulpo, anchoa.
**Ventresca de atún.** La parte más jugosa del atún —aquí no es atún rojo, sino atún fresco de aleta amarilla—, la que tiene la veta de grasa que se deshace. Se suele servir a la plancha o cocinada muy poco, dejando el centro casi crudo, para no perder la grasa. Es, posiblemente, el corte que mejor lleva la cocina de Paco con poca intervención.
**Rosada a la plancha o frita.** Pescado blanco económico pero noble si está fresco. La opción de plancha o frita, dada al cliente, es una decisión interesante: significa que el chef confía en el producto en ambos formatos. Pocas cocinas dan esa libertad sin mancharse.
**Araña frita.** El pescado de roca con peor fama y mejor carne. Tiene espinas dorsales venenosas (de ahí el nombre), pero limpiada y frita es una de las frituras más memorables del Mediterráneo. Quien la pone en la pizarra está diciendo que sabe lo que se trae entre manos.
**Calamares fritos.** Plato de prueba para una cocina. Calamares mal fritos se notan a la primera: aceite reusado, rebozado plastificado, exceso de fritura. En El Gallo (y esto se confirmará en una entrega futura con más detalle) tienen el rebozado correcto, justo el tiempo de fritura, sin aceite de más en el plato.
**Bacalao frito.** El bacalao desalado, rebozado finamente y frito en aceite limpio, con la piel crujiente y el centro jugoso, es uno de los puntos altos de la cocina del sur. En tapa o en ración, se queda en la memoria. Lo voy a desarrollar entero en uno de los posts de la serie.
**Pulpo frito.** Una versión menos común que el pulpo a la gallega o a la brasa: cocer el pulpo y luego freírlo en taco. Cuando se hace bien, contrasta gelatinoso por dentro y crujiente por fuera en cada bocado. Cuando se hace mal, queda chicloso. La duda solo se resuelve probándolo.
**Anchoas del Cantábrico.** Si las pides aquí, traen anchoa de las buenas: ese filete oscuro y curado en sal, con el aceite que las acompaña, que se untaría con el dedo si nadie mirara. No es plato de cocina, es plato de despensa, pero las despensas también se eligen.
**Buñuelos de bacalao.** Masa con bacalao desmigado, fritos en bolitas. Si la masa es ligera y el bacalao está bien desalado, son demoledores. Si la masa pesa o el bacalao está salado, son una decepción. Es uno de esos platos donde el chef se desnuda. Uno de los favoritos para abrir comida.
**Pinchos de gambas.** Brocheta de gambas a la plancha. Plato simple que se vuelve memorable solo si la gamba es buena y el punto del fuego es exacto. La sencillez aparente de este plato esconde lo difícil que es hacerlo bien.
**Vieiras al ajillo.** Las vieiras no son fáciles. Sobrecocidas se quedan duras, mal limpias amargan. Aquí las tienen al ajillo, lo que sugiere control técnico y respeto al producto. Quien sirve vieiras en una pizarra es porque puede.
### Carnes
El bloque de carnes es donde más se nota la firma personal de Paco. Convive lo tradicional (callos, magro con tomate, albóndigas) con lo más sofisticado (solomillo al Pedro Ximénez, secreto ibérico, pastela de pollo).
**Pinchos de cordero.** Cordero en brocheta, marinado y a la brasa. Plato que se vende solo en cualquier sitio donde el corte sea bueno. La elección del cordero (lechazo, recental, lechal, pascual) es una pequeña decisión que cambia el plato; quiero entender qué cordero usan aquí, será motivo de pregunta en la próxima visita.
**Albóndigas en salsa de almendras.** Receta clásica andaluza. Las almendras dan profundidad sin pesar, no como una mantequilla. Plato que requiere paciencia: la salsa hay que reducirla con cuidado, y el majado de las almendras tiene que estar molido fino pero no convertido en pasta. Una de las recetas más antiguas de la cocina andaluza, herencia mora directa.
**Magro con tomate.** El plato hogareño por antonomasia. Cuando se hace en casa de la abuela, es una cosa. Cuando un chef serio lo mete en su pizarra, es porque está convencido de su versión. Pones eso en la pizarra solo si te apañas con el tomate.
**Pollo al curry.** Apertura a curris como guiño internacional sin perder la cocina local. La pizarra incluye, además, la **pastela de pollo al curry**, que es harina de otro costal: la pastela es una preparación de origen marroquí, hojaldre dulce-salado, que pide más manos que un curry suelto. Tener las dos cosas en la misma pizarra es una declaración: aquí no nos limitamos a la cocina de la abuela, aunque la respetamos.
**Pollo a la pimienta.** Salsa con crema, pimienta verde o negra triturada, reducción de fondo oscuro. Plato de bistró francés que un chef andaluz puede levantar muy alto. Cuando lo pruebe en serio (otra entrega), te lo cuento.
**Secreto ibérico.** Corte premium del cerdo ibérico, la zona del cuello con infiltración de grasa que se deshace en boca. A la plancha es el plato más fácil del mundo y a la vez el más difícil: se hace o se quema en treinta segundos. **Va a tener post propio en esta serie**, porque la diferencia entre un secreto bueno y un secreto memorable está en detalles que merecen explicarse despacio.
**Solomillo al Pedro Ximénez.** La salsa PX es de las grandes salsas de la cocina andaluza moderna. Reducir un buen Pedro Ximénez con un caldo oscuro hasta lograr una consistencia cremosa, brillante, dulce-salada que se enrosca en la cuchara. Solomillo a la plancha, salsa por encima. Si el PX es de bodega seria (y aquí, sospecho que sí), el plato vuela. **Otro post propio.**
**Callos.** Con sus garbanzos, su chorizo, su morcilla, su pata. Plato de invierno que un chef serio no quita de la pizarra ni en mayo, porque hay público que lo busca todo el año. Mantenerlos en mayo es una concesión al cliente fiel. Y, hecho como hay que hacerlo, los callos en mayo son tan ricos como en enero.
**Corazón de atún encebollado.** El corazón de atún es despiece menor, denso, sabroso. Encebollado a fuego lento, con la cebolla pochada hasta el caramelo y el corazón cortado fino. Tapa de barra de Cádiz que ha llegado a Las Lagunas de Mijas en versión digna. Aquí veo cocina con cabeza: aprovechar despieces que casi nadie pide y darles dignidad.
### Vegetales, huevos, sorpresas
Esta es la zona más atrevida de la pizarra. Hay platos que muchos sitios no se atreven a poner.
**Alcachofas con brandada de bacalao.** Combinación moderna y elegante. Alcachofas (carnosas, ligeramente amargas) sobre brandada (bacalao desmigado en una emulsión de aceite y patata, técnica de origen provenzal). El contraste de texturas y sabores es el tipo de cosas que un chef pone en carta cuando confía en su producto y en su técnica al mismo tiempo.
**Pisto de boletus y huevo campero.** Versión otoñal del pisto, sustituyendo verduras por boletus. El huevo campero (yema naranja, blanco firme) cuajado encima cierra el plato. Muy de cocina actual, muy de chef que ha leído. Que esto esté en una pizarra de peña flamenca, en mayo, ya dice cosas.
**Bastones de berenjena.** El plato gancho. Berenjena cortada en bastón, frita, normalmente con miel de caña por encima. Plato fácil de hacer mal (queda aceitoso) y fácil de hacer bien si se respeta el tiempo en la freidora y la fritura es limpia. Plato que ya defenderé en su entrega.
**Porra.** De Antequera. Versión espesa del salmorejo, con miga de pan en mayor proporción, atún o jamón por encima, huevo duro picado. Plato que en mayo, fresco, es de los más reconfortantes que existen. Detalles a vigilar: aceite de oliva (que no sea cualquier aceite), tomate maduro de verdad, ajo justo.
**Paté casero con mermelada de tomate.** El "casero" hay que verlo, pero si lo es (y aquí sospecho que lo es), la decisión de acompañarlo con mermelada de tomate en lugar del clásico membrillo es una decisión que dice cosas. Puntos por la diferencia.
**Torta del Casar.** Queso de oveja extremeño, denso, untuoso. No se cocina, se sirve. Lo que diga aquí del plato depende solo de **si la torta está en su punto** (ni dura, ni pasada de fermentación). Más de lo mismo: producto.
**Morcilla de arroz.** La morcilla de Burgos, frita o al horno, con su intenso sabor a sangre, cebolla, especias y arroz. Tapa demoledora cuando está hecha al punto. Tapa decepcionante si está poco o muy hecha. El margen es estrecho.
**Huevos de gansa con picadillo ibérico y guiso de guisantes.** El plato más sofisticado de la pizarra. Huevo de gansa (más grande, más cremoso, más graso que el de gallina), picadillo ibérico (de cerdo, muy especiado), guiso de guisantes que aporta dulzor vegetal. Es el plato que demuestra ambición. Un chef que pone esto en pizarra está diciendo "yo quiero, y puedo". Lo voy a probar como plato único en una próxima visita y lo voy a contar bien.
**Arroz con carrillada.** Carrillada de cerdo (o ternera) cocida largo a baja temperatura, deshilachada o en taco, sobre un arroz que ha recogido el jugo del estofado. Plato de cuchara con horas detrás: la carrillada pide tiempo para soltar el colágeno y quedarse melosa, y el arroz necesita el caldo justo para no pasarse. Cuando ambos cuadran, es uno de esos platos que justifican la sobremesa.
## Lo que viene en la serie
Esta entrega es la presentación. En las próximas iré soltando deep-dives sobre platos concretos: cómo se hacen, qué los distingue, cómo se acompañan, qué pequeño detalle de la versión de El Gallo los hace memorables. Pongo aquí, sin compromiso de orden, los posts que tengo en la cabeza:
- **El secreto ibérico en El Gallo.** Corte, plancha, acompañamientos, qué pedir como guarnición para no romper el plato.
- **Solomillo al Pedro Ximénez.** La salsa: bodega, reducción, temperatura. Por qué un PX de supermercado nunca dará el mismo resultado que un PX de bodega seria.
- **Buñuelos de bacalao.** Masa, desalado del bacalao, temperatura del aceite. El triángulo donde se pierde o se gana este plato.
- **Bastones de berenjena con miel de caña.** Detalles que separan un plato bueno de uno memorable.
- **Pisto de boletus y huevo campero.** Cocina actual escondida en una pizarra de peña.
- **Corazón de atún encebollado.** El despiece menor del atún (aquí, de aleta amarilla) y por qué hay que aprender a pedirlo en zonas como esta.
Y, en paralelo, iré escribiendo sobre el ambiente: las tardes en la peña, los socios habituales, los días en los que sale alguien a cantar por bulerías al final del servicio. Una peña flamenca no es solo lo que se come; es lo que pasa alrededor de la mesa, y eso también merece su propia entrega.
## Para terminar (de momento)
Si tuviera que recomendar **un solo sitio** para alguien que viene a Mijas y quiere comer bien sin pretensiones, hoy ya recomendaría la peña flamenca El Gallo. Y eso lo digo después de muchos años recomendando otros sitios, algunos muy buenos, en este mismo blog. La cocina de Paco Flores merece la pena, y el equipo que sostiene la sala merece la mención que se le da en este post y en los que vendrán.
Si vas, **tres consejos prácticos**:
1. **Pregunta por los formatos** antes de pedir. La pizarra no especifica si cada plato es tapa, media o ración entera; algunos se pueden pedir en cualquier formato. Pedir sin preguntar te puede dejar corto o pasado.
2. **Pide variedad para compartir.** En una mesa de cuatro, lo razonable es entre seis y ocho platos repartidos: un par de pescados, un par de carnes, alguna cosa vegetal y una sorpresa de la pizarra. La variedad es la gracia.
3. **Reserva si vas un viernes o sábado por la noche.** El sitio se llena, y es un sitio donde acabas charlando con la mesa de al lado y comiendo despacio. Si llegas a las nueve sin reserva, esperarás seguro.
Y si te suena flamenco de fondo, no te muevas. La peña es lo que tiene. Estás justo donde tienes que estar.
La próxima entrega de la serie: la alcachofa con brandada de bacalao, en detalle. Detrás vendrán el secreto ibérico, el solomillo al Pedro Ximénez y unos cuantos más. La iremos publicando con calma a lo largo de las próximas semanas. Si te interesa el tema, suscríbete al RSS y te van llegando.
---
# Una tarde en La Cañada de Marbella: pasillos estrechos, escaleras imposibles y una hamburguesa cara
URL: https://javiervalencia.net/post/una-tarde-en-la-canada-de-marbella
Salir un sábado por la tarde a La Cañada con cinco personas no es un plan, es un proyecto logístico. Lo digo después de haberlo hecho varias veces y de haber salido siempre con la sensación de que el centro comercial está pensado para que el visitante se rinda antes de llegar a la tienda que tenía en la cabeza. Esta es la crónica de la última vez, con sus alegrías, sus disgustos, y una cuenta de Five Guys que todavía no me he recuperado.
## La primera batalla: aparcar y entrar
La Cañada tiene parking gratuito, lo cual es una de sus mejores virtudes en una zona donde aparcar en la calle es ciencia ficción de mayo a octubre. La trampa está en que un sábado a las seis de la tarde, los aparcamientos cubiertos están llenos y los descubiertos están a pleno sol con el coche convertido en horno. Hay que dar varias vueltas, esquivar a los que van marcha atrás sin mirar, y rezar para que alguien salga justo cuando tú llegas.
Una vez dentro, la primera sensación es que **el centro comercial fue diseñado en una época en la que pensaban que iría menos gente**. Las entradas embudan, los pasillos centrales se estrechan justo donde se cruzan dos zonas de tiendas, y los carteles de orientación están colocados a una altura en la que un grupo de adolescentes parados delante los tapa por completo.
## Las escaleras: un misterio arquitectónico
Llevo años yendo a La Cañada y todavía no tengo claro cómo se sube de la planta baja a la superior sin dar un rodeo de doscientos metros. Las escaleras mecánicas no están donde uno espera. No están a la entrada, no están en el centro, no están al final de los pasillos largos. Están en sitios raros, normalmente medio escondidas detrás de una columna o en un recodo que no se ve hasta que pasas por delante.
Si vas con tres niñas, una de ellas con el carrito de un peluche que se ha empeñado en sacar de casa, y otra con un helado a medio terminar, llegar a unas escaleras se convierte en un ejercicio de paciencia. Y cuando por fin las encuentras, casi siempre son **las que suben**. Para bajar tienes que cruzar de nuevo a la otra punta. Es como si el arquitecto hubiera diseñado el flujo pensando en aves migratorias y no en familias que quieren entrar a una tienda concreta y salir.
Hay ascensores, sí, pero también escasos y casi siempre con cola. Tres familias con carrito esperando un ascensor que solo cabe una, y una pareja entrando con dos bolsas grandes "porque les pillaba mejor". El protocolo del ascensor en un centro comercial saturado merece un post aparte.
## La masificación
Soy de los que prefieren las conversaciones tranquilas y los sitios poco concurridos. La Cañada un sábado por la tarde es exactamente lo contrario. Hay una densidad de gente que recuerda al metro de Madrid en hora punta, pero con la diferencia de que en el metro todos van en la misma dirección y aquí todos van en direcciones distintas a la vez. Niños sueltos, grupos de adolescentes parados en mitad del pasillo mirando el móvil, parejas con carrito que se quedan delante de un escaparate sin avisar, repartidores con cajas que tienen que pasar a la fuerza.
Para quien va con una idea clara de lo que quiere, cada metro es un examen de paciencia. Para quien va a pasear sin objetivo, supongo que es divertido. Yo llevo años viviendo en la Costa del Sol y todavía no he aprendido a disfrutar de pasear entre escaparates con quinientas personas haciendo lo mismo a un metro de distancia.
## La Casa del Libro: el oasis
Por suerte hay rincones donde el ruido baja de golpe. La Casa del Libro es uno de ellos. Entras, se cierra la puerta, y la masificación queda fuera. La librería de La Cañada no es la más grande del mundo, pero está bien organizada, con buenas mesas de novedades en la entrada y un fondo decente en literatura y ensayo.
Para Penélope es la parada obligatoria. Cada vez que vamos, sale con al menos un libro debajo del brazo, normalmente alguno de fantasía juvenil o de las sagas que ahora le dan por leer del tirón. Lo bueno de esta edad es que el ritmo de lectura es brutal: lo que en un adulto es un libro de dos semanas, en ella es un libro de dos tardes. La Casa del Libro es de los pocos sitios del centro comercial donde **la veo concentrarse de verdad**, leyendo la contraportada, abriendo el libro al azar, comparando dos ediciones distintas. Si le diera el dinero, saldría con quince. Si le doy la opción, sale con tres y los devora antes del fin de semana siguiente.
Hay una sección de papelería bastante completa, además, y la zona infantil es lo bastante grande como para que las pequeñas se entretengan mientras la mediana decide qué se lleva. Por valor de refugio frente a la masificación, La Casa del Libro vale el viaje aunque no compres nada.
## Five Guys: la cuenta que no me esperaba
Después de la librería tocaba comer. La idea era algo rápido, sin protocolos, y como las niñas votaron por hamburguesas, acabamos en Five Guys. Era la primera vez que íbamos los cinco juntos. Sospechaba que no era barato, pero no me esperaba lo que pasó al pedir la cuenta.
**Salimos a veinte euros por cabeza.** Cinco personas, cien euros. Por una hamburguesa, una ración de patatas (eso sí, generosa, comparten dos personas sin problema) y una bebida. Sin postre, sin entrante, sin extras raros. Hamburguesa, patatas, refresco, fin.
No digo que la hamburguesa esté mal. Está bien hecha, los ingredientes son frescos, las patatas tienen su gracia con esa cantidad ridícula que te ponen en el cucurucho. Pero veinte euros por cabeza es el precio de una comida en un restaurante con mantel y servicio. Y aquí pides en una cola, te dan un número, vas tú a buscar la bandeja, te sientas en una mesa de plástico, y limpias tú la bandeja al irte. La proporción precio/experiencia no me cuadra.
Para que las cuentas salgan, tendría que ser **la mejor hamburguesa de mi vida**, y no lo es. Es una buena hamburguesa, pero hay sitios en Marbella donde por ese dinero te tomas una hamburguesa equivalente sentado, con servicio en mesa, copa de vino y postre incluido. La diferencia, supongo, es que Five Guys es marca, y la marca cobra. Yo, después de esta experiencia, los próximos sábados de hamburguesa los vamos a hacer en otro sitio.
## La joyería de la esquina: la sorpresa buena
La tarde la salvó algo que no estaba en el plan. En la planta superior, en una esquina al final de uno de los pasillos largos, hay una joyería pequeña que llevábamos tiempo viendo de pasada y nunca habíamos entrado. Esta vez, una de las niñas necesitaba arreglar un colgante que se le había roto, y entramos a preguntar si lo hacían.
La atención fue, sin exagerar, la mejor que hemos tenido en cualquier tienda de La Cañada. La señora que estaba detrás del mostrador no nos miró por encima del hombro porque íbamos en chanclas y con cara de cansados. Tomó el colgante, lo examinó con calma, explicó qué tenía que hacer, dio un precio razonable y un plazo concreto. Mientras tanto, atendió a la pequeña que quería ver "el anillo brillante de allí", se lo dejó probar, le siguió la conversación, y nos dejó mirar el resto sin presión ninguna.
Salimos sin haber comprado nada caro, pero con el colgante anotado para recogerlo la semana siguiente y con la sensación rara, en un centro comercial, de **haber sido tratados como personas y no como tickets de caja**. Hay tiendas que parecen pensar que el cliente es un trámite. Esta señora no.
## Para terminar
La Cañada tiene cosas buenas. El parking gratuito, la oferta de tiendas, La Casa del Libro como refugio, y joyas escondidas como esa joyería del piso de arriba. Pero la experiencia general un sábado por la tarde, con familia y prisas relativas, es agotadora. Las escaleras mal puestas, los pasillos que se estrechan en los peores sitios, la densidad de gente, y precios como los de Five Guys que dejan a uno preguntándose si no compensaba volver a casa y hacerse unas hamburguesas en la plancha.
La próxima vez iremos un martes a media mañana, entraremos directos a la librería, saludaremos a la señora de la joyería si está, y compraremos las hamburguesas en el supermercado de vuelta. La Cañada tiene su gracia, pero hay que aprender a domesticarla.
---
# PostgreSQL desde cero (IV): índices, EXPLAIN y rendimiento
URL: https://javiervalencia.net/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento
*Cuarta entrega de la serie **[PostgreSQL desde cero a pro](/search?tag=postgresql-desde-cero)**. Tiempo de lectura estimado: 13 minutos.*
Tienes un esquema decente ([II](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones)) y sabes escribir consultas potentes ([III](/post/postgresql-desde-cero-iii-ctes-window-functions-y-consultas-avanzadas)). En algún momento, algo irá lento. Este post es sobre cómo averiguar por qué y qué hacer al respecto.
Tres ideas guían todo lo que sigue:
1. **Mide antes de optimizar.** `EXPLAIN ANALYZE` es tu única fuente de verdad.
2. **Los índices aceleran lecturas y ralentizan escrituras.** No los añadas por reflejo.
3. **PostgreSQL es autogestionado pero no mágico.** `ANALYZE` y `VACUUM` son las dos palabras más importantes.
## Tipos de índices
PostgreSQL ofrece varios tipos de índice. El 95% de las veces el que quieres es B-tree. Los demás resuelven casos específicos.
### B-tree
Por defecto. Soporta igualdad, rangos y ordenación. Sirve para prácticamente todo.
```sql
CREATE INDEX posts_author_id_idx ON posts(author_id);
CREATE INDEX posts_published_at_idx ON posts(published_at DESC);
```
#### Índices compuestos
El orden de las columnas importa:
```sql
CREATE INDEX posts_author_published_idx ON posts(author_id, published_at DESC);
```
Este índice ayuda a:
- `WHERE author_id = ?` (prefijo)
- `WHERE author_id = ? AND published_at > ?` (prefijo + rango)
- `WHERE author_id = ? ORDER BY published_at DESC` (sin sort extra)
No ayuda a `WHERE published_at > ?` por sí solo: el prefijo es `author_id`.
Regla práctica: **igualdad antes que rango**. Columnas que filtras por igualdad primero, las de rango al final.
#### Índices parciales
Solo indexan las filas que cumplen una condición:
```sql
CREATE INDEX posts_published_idx
ON posts(published_at DESC)
WHERE status = 'published';
```
Resultado: índice mucho más pequeño, consultas sobre posts publicados mucho más rápidas. Si el 90% son drafts, un índice parcial puede ser 10× más pequeño y proporcionalmente más rápido.
#### Índices por expresión
Permiten indexar el resultado de una función:
```sql
CREATE INDEX users_email_lower_idx ON users(lower(email));
-- Esta consulta usa el índice
SELECT * FROM users WHERE lower(email) = 'a@b.com';
```
Si tu WHERE aplica una función, necesitas un índice por esa función. Sin él, el índice normal no se usa.
### GIN
Para tipos compuestos: arrays, JSONB, full text search, tsvector.
```sql
-- Array
CREATE INDEX posts_tags_gin ON posts USING GIN (tags);
SELECT * FROM posts WHERE tags @> ARRAY['postgresql'];
-- JSONB
CREATE INDEX events_data_gin ON events USING GIN (data jsonb_path_ops);
SELECT * FROM events WHERE data @> '{"plan":"free"}';
-- Full text search
CREATE INDEX posts_search_gin ON posts USING GIN (search);
SELECT * FROM posts WHERE search @@ plainto_tsquery('postgresql');
```
### GiST
Para tipos geométricos, rangos y búsquedas aproximadas. La extensión `btree_gist` permite mezclarlo con igualdades (lo vimos en la [entrega II](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones) con las exclusion constraints).
### BRIN
Índice "block range". Muy pequeño, ideal para tablas enormes con datos físicamente correlacionados con la columna indexada (típicamente tablas *append-only* por fecha).
```sql
CREATE INDEX events_created_at_brin ON events USING BRIN (created_at);
```
BRIN es el patrón para tablas de logs que no caben en RAM: el índice ocupa KBs en lugar de GBs. La contrapartida es que es menos selectivo.
### Hash
Antes de PostgreSQL 10 no se recomendaba. Hoy es WAL-loggeado y puede usarse para igualdad estricta de valores grandes. En la práctica, B-tree sigue siendo la elección por defecto salvo casos muy específicos.
### Covering indexes
Desde PostgreSQL 11, un índice puede "incluir" columnas adicionales que no son claves pero están disponibles en el índice, permitiendo *index-only scans*:
```sql
CREATE INDEX posts_slug_covering
ON posts(slug) INCLUDE (title, published_at);
```
Si la consulta pide solo `slug`, `title` y `published_at`, PostgreSQL puede responder sin tocar la tabla.
## EXPLAIN
`EXPLAIN` muestra el plan de ejecución sin ejecutar la consulta:
```sql
EXPLAIN SELECT * FROM posts WHERE author_id = 42;
```
Salida típica:
```
Index Scan using posts_author_id_idx on posts (cost=0.29..8.31 rows=1 width=...)
Index Cond: (author_id = 42)
```
- `cost=X..Y`: coste estimado. No son milisegundos; son "unidades de página" del planner.
- `rows=N`: filas estimadas.
- `width=B`: bytes por fila estimados.
Si solo tienes `EXPLAIN`, estás viendo estimaciones. Para medir de verdad, `EXPLAIN ANALYZE`:
```sql
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM posts WHERE author_id = 42;
```
- `ANALYZE`: ejecuta la consulta de verdad.
- `BUFFERS`: añade información sobre páginas leídas (hit/read/dirtied).
Información clave que aparece con ANALYZE:
- `actual time=X..Y rows=N loops=M`: tiempo real y número de filas por iteración.
- `Planning Time` y `Execution Time`: totales al final.
### Banderas rojas en un plan
- **Seq Scan sobre tabla grande con filtro selectivo**: falta un índice adecuado.
- **`rows` estimadas muy distintas de las reales**: estadísticas desactualizadas, haz `ANALYZE`.
- **Hash join con `Batches: N`** alto: no cabe en `work_mem`, se está usando disco.
- **Filter vs Index Cond**: un `Filter` se aplica después del scan; un `Index Cond` lo usa para saltar. Siempre prefieres el segundo.
- **Nested Loop sobre muchas filas**: a veces el planner se equivoca; revisa estadísticas y costes.
### Visualizadores
Los planes se vuelven ilegibles rápido. Herramientas útiles:
- [explain.depesz.com](https://explain.depesz.com) — pegas el plan y lo colorea por coste.
- [explain.dalibo.com](https://explain.dalibo.com) — visualización de árbol con detalles.
- [pev2](https://dalibo.github.io/pev2/) — versión moderna de lo anterior.
Para un plan que te está volviendo loco, pegarlo ahí suele dar el "ajá" en segundos.
## ANALYZE y estadísticas
PostgreSQL elige planes basándose en **estadísticas** sobre la distribución de los datos: cardinalidad, correlación física, histogramas, most common values (MCV).
Esas estadísticas las mantiene `ANALYZE` (y el `autovacuum`, que las actualiza automáticamente). Si cargas muchos datos de golpe, lanza `ANALYZE` a mano:
```sql
ANALYZE posts;
ANALYZE; -- toda la base de datos
```
Para columnas con correlaciones o distribuciones difíciles, puedes crear **estadísticas extendidas**:
```sql
CREATE STATISTICS posts_author_status (dependencies, ndistinct)
ON author_id, status FROM posts;
ANALYZE posts;
```
Esto le enseña al planner que `author_id` y `status` no son independientes, que es lo que suele romper las estimaciones con `AND` de columnas correlacionadas.
## VACUUM y autovacuum
PostgreSQL usa MVCC: cada `UPDATE` o `DELETE` deja la fila vieja como "muerta" hasta que `VACUUM` la limpia. Si el `autovacuum` no da abasto, la tabla se **infla** y las consultas se degradan.
Síntomas de bloat:
- Tabla mucho más grande que la suma de sus filas.
- `EXPLAIN ANALYZE` muestra muchas más páginas leídas de las esperadas.
- El `autovacuum_worker` sale disparado en logs.
Diagnóstico rápido:
```sql
SELECT
schemaname,
relname,
n_live_tup,
n_dead_tup,
round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 2) AS dead_pct,
last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
```
Si ves tablas con más del 20% de tuplas muertas y `last_autovacuum` antiguo, hay trabajo.
Ajustes típicos para tablas con mucha escritura:
```sql
ALTER TABLE events SET (
autovacuum_vacuum_scale_factor = 0.02,
autovacuum_analyze_scale_factor = 0.01
);
```
El autovacuum se dispara cuando el porcentaje de tuplas muertas supera `scale_factor * n_live_tup + vacuum_threshold`. Bajar el scale_factor en tablas calientes evita el bloat.
`VACUUM FULL` recompacta pero bloquea la tabla entera. En producción, casi nunca. Alternativas: `pg_repack`, `pg_squeeze`.
## Parámetros de servidor que más se notan
Estos son los parámetros de `postgresql.conf` que marcan la diferencia real:
- **`shared_buffers`**: memoria de caché compartida. Regla de pulgar: **25% de la RAM total**.
- **`effective_cache_size`**: lo que el planner cree que está disponible entre `shared_buffers` y el page cache del kernel. **50–75% de la RAM**.
- **`work_mem`**: memoria por operación de ordenación/hash. Empieza por **16–64 MB**, súbelo con cautela (se multiplica por conexiones × operaciones por consulta).
- **`maintenance_work_mem`**: para `VACUUM`, `CREATE INDEX`, `REINDEX`. **256 MB – 2 GB**.
- **`max_connections`**: bajo, mejor. Usa un **pool** (PgBouncer) antes que 1000 conexiones.
- **`random_page_cost`**: `1.1` en SSD (el default 4 asume HDD).
- **`effective_io_concurrency`**: `200` en SSD, `1` en HDD.
- **`wal_compression`**: `on` si tienes CPU sobrada (ahorra WAL).
- **`checkpoint_timeout`** y **`max_wal_size`**: afectan a la carga de I/O. Ajústalos si ves "checkpoints occurring too frequently" en logs.
Y en el servicio de sistema, casi siempre:
- `huge_pages = try` y configurar `vm.nr_hugepages` en el SO para tablas grandes.
- `vm.swappiness = 10`.
## Herramientas de diagnóstico continuo
### pg_stat_statements
Extensión (viene con PostgreSQL) que guarda estadísticas de cada consulta normalizada: llamadas totales, tiempo medio, filas.
```sql
CREATE EXTENSION pg_stat_statements;
-- Top 20 consultas por tiempo total
SELECT
substring(query, 1, 80) AS query,
calls,
round(total_exec_time::numeric, 1) AS total_ms,
round(mean_exec_time::numeric, 2) AS mean_ms,
rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
```
Es la manera más rápida de encontrar la consulta que lo está petando.
### Tablas de sistema más útiles
```sql
-- Consultas activas ahora mismo
SELECT pid, state, wait_event, age(clock_timestamp(), query_start) AS dur,
query
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY dur DESC;
-- Bloqueos
SELECT blocked.pid AS blocked_pid, blocking.pid AS blocking_pid,
blocked.query AS blocked_query
FROM pg_stat_activity AS blocked
JOIN pg_stat_activity AS blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid));
-- Uso de índices
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
ORDER BY idx_scan ASC; -- los menos usados
```
Índices con `idx_scan = 0` durante meses son candidatos a eliminar: ocupan espacio y ralentizan escrituras para nada.
### Connection pooling
Nunca dejes que una app abra conexiones directas a PostgreSQL en producción. Pon **PgBouncer** (o similar) delante. Modos:
- `session`: connection pool clásico.
- `transaction`: reutiliza conexión por transacción. Mucho más eficiente pero rompe ciertas features de sesión.
- `statement`: reutiliza por consulta. Solo para patrones muy específicos.
El impacto es enorme: 5000 conexiones lógicas de aplicación pueden respaldarse con 20 conexiones reales a PostgreSQL.
## Una receta de depuración
Cuando una consulta va lenta, el orden que sigo:
1. `EXPLAIN ANALYZE (BUFFERS)` y mirar si las estimaciones cuadran con la realidad.
2. Si no cuadran: `ANALYZE`, y si sigue sin cuadrar, estadísticas extendidas.
3. Buscar Seq Scan sobre tabla grande → índice que falta.
4. Buscar `Filter` que se aplica tras el scan → índice por expresión o parcial.
5. Buscar `Sort` en memoria grande → `work_mem` insuficiente o `ORDER BY` reemplazable por índice.
6. Buscar joins con `rows` muy sobreestimadas → reformular o forzar join type.
7. Si todo está bien y sigue siendo lento por volumen: **agregados precalculados** (materialized views, resúmenes incrementales).
Si los agregados precalculados empiezan a doler, quizá sea momento de un motor analítico al lado. Lo vimos en la [serie ClickHouse](/search?tag=clickhouse-desde-cero), especialmente el [post comparativo](/post/clickhouse-para-desarrolladores-que-vienen-de-postgresql).
## Por dónde seguir
- **[V: replicación, backups y producción](/post/postgresql-desde-cero-v-replicacion-backups-y-produccion)** — último tramo: operar PostgreSQL con garantías.
Aterrizando tarde:
- [I: instalación, psql y primeros pasos](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos)
- [II: tipos, restricciones y relaciones](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones)
- [III: CTEs, window functions y consultas avanzadas](/post/postgresql-desde-cero-iii-ctes-window-functions-y-consultas-avanzadas)
---
# Git avanzado III: entornos y repos grandes
URL: https://javiervalencia.net/post/git-avanzado-iii-entornos-y-repos-grandes
*Tercera y última entrega de la serie **[Git avanzado](/search?tag=git-avanzado)** — y cierre del recorrido completo que empezó con **[Git básico](/search?tag=git-basico)** y pasó por **[Git intermedio](/search?tag=git-intermedio)**. Tiempo de lectura estimado: 5 minutos.*
Para cerrar la serie, tres comandos que la mayoría de desarrolladores no tocan hasta que los necesitan, pero que cuando llega el momento resuelven problemas que no tienen otra buena solución. `git worktree` para tener varias ramas checked out simultáneamente, `git submodule` para incluir otros repos dentro del tuyo, y `git sparse-checkout` para trabajar con monorepos sin descargar el mundo entero.
## `git worktree`: varias ramas a la vez
El problema clásico: estás trabajando en `feature/carrito` y entra un bug urgente en producción. Lo normal: `git stash`, `git switch main`, arreglas, commiteas, pusheas, vuelves. Si estás compilando algo pesado o tienes un dev server corriendo, cambiar de rama lo rompe todo.
`git worktree` resuelve esto creando un **segundo directorio** con un checkout independiente del mismo repo:
```bash
git worktree add ../proyecto-hotfix main
```
Resultado: al lado de tu directorio original aparece `proyecto-hotfix/` con el contenido de `main`. Las dos copias comparten el `.git/`, así que no ocupa apenas espacio extra. Puedes trabajar en las dos en paralelo, cada una con su terminal, su dev server, su IDE.
Flujo real:
```bash
# estoy en ~/proyectos/blog, en feature/admin
git worktree add ../blog-hotfix -b hotfix/login-roto origin/main
cd ../blog-hotfix
# ... arreglo, commit, push ...
cd ../blog
git worktree remove ../blog-hotfix
git branch -d hotfix/login-roto
```
Comandos básicos:
```bash
git worktree list # ver todos los worktrees
git worktree add ruta rama # crear worktree
git worktree add ruta -b nueva # crear worktree con rama nueva
git worktree remove ruta # eliminar (aviso si hay cambios)
git worktree prune # limpiar referencias a worktrees borrados
```
Restricciones:
- **No puedes tener dos worktrees con la misma rama checked out.** Si `feature/X` está en un worktree, otro worktree no puede estar también en `feature/X`.
- **El worktree principal (tu clone original) no se puede mover ni borrar fácilmente.** Los secundarios sí.
Cuándo usarlo:
- Hotfix mientras trabajas en una feature sin tirar de stash.
- Comparar el comportamiento de dos ramas ejecutándolas al mismo tiempo.
- Tener un worktree por versión mantenida: `~/blog-main`, `~/blog-v1.x`, `~/blog-v2.x`.
- Sesiones de review largas: abres el PR en un worktree aparte, pruebas, sin molestar tu trabajo habitual.
Para mí, worktree es de los comandos que, una vez descubiertos, reemplazan al 80% de los usos de stash.
## `git submodule`: repos dentro de repos
Un submódulo es un repositorio git que vive dentro de otro repositorio git, apuntando a un commit específico. El repo "padre" no guarda los ficheros del submódulo, sino una referencia (commit hash) del repo "hijo".
Casos donde se usa:
- Tu proyecto incluye una librería propia que mantienes en un repo aparte.
- Tienes un tema o plugin de terceros que quieres fijar a una versión.
- Dos equipos contribuyen a una dependencia compartida que se integra en varios proyectos.
Añadir un submódulo:
```bash
git submodule add https://github.com/usuario/libreria libs/libreria
git commit -m "Añadir submódulo libs/libreria"
```
Esto crea un fichero `.gitmodules` en la raíz con la configuración, más un "puntero" en `libs/libreria` que no es una carpeta normal sino una referencia a un commit.
Al clonar un repo con submódulos:
```bash
git clone URL proyecto
cd proyecto
git submodule update --init --recursive
```
O en un paso:
```bash
git clone --recurse-submodules URL proyecto
```
Actualizar el submódulo a una versión nueva:
```bash
cd libs/libreria
git fetch
git checkout v2.1.0
cd ../..
git add libs/libreria
git commit -m "Subir libreria a v2.1.0"
```
El commit que registras en el repo padre no contiene los ficheros del submódulo: solo el hash del commit de `libs/libreria` que le corresponde.
Comandos que se usan:
```bash
git submodule status # versiones actuales de cada submódulo
git submodule update # sincroniza los submódulos con lo comiteado
git submodule foreach 'git pull' # ejecuta comandos en todos los submódulos
```
**Honestamente**: submódulos son potentes pero pesados. Atasques típicos: alguien hace commit sin actualizar los submódulos, clones que se olvidan del `--recurse-submodules`, conflictos raros entre el puntero y la rama del submódulo. Antes de adoptarlos, considera alternativas: paquetes en un registry privado, monorepo con todo junto, [Go workspaces](/search?q=go+workspaces) o equivalentes del lenguaje. Submódulos son la solución cuando no hay otra mejor, no el primer recurso.
## `git sparse-checkout`: descargar solo lo que te interesa
En un monorepo gigante (cientos de miles de ficheros, varios gigas) no necesitas tenerlo todo checked out para trabajar en la parte de `frontend/`. `sparse-checkout` le dice a git qué partes del working tree quieres que existan en disco. El `.git` completo sigue ahí, pero el working tree solo tiene lo que pides.
Habilitarlo en un repo existente:
```bash
git sparse-checkout init --cone
git sparse-checkout set frontend/app frontend/shared tools/build
```
A partir de ese momento, tu working tree solo contiene esas rutas. El resto del repo está en `.git/` pero no aparece como ficheros.
Con `--cone` (recomendado) funciona en modo "directorios": le das rutas completas y git incluye todo lo que cuelga de ellas. Sin `--cone` es modo "patterns" (tipo gitignore al revés), más flexible pero mucho más propenso a sorpresas.
Comandos:
```bash
git sparse-checkout init --cone # activar
git sparse-checkout set ruta1 ruta2 # establecer rutas visibles
git sparse-checkout add otra-ruta # añadir rutas
git sparse-checkout list # ver rutas actuales
git sparse-checkout disable # todo el repo de vuelta
```
Combinado con `--filter=blob:none` al clonar, la reducción es enorme:
```bash
git clone --filter=blob:none --sparse URL proyecto
cd proyecto
git sparse-checkout set frontend/app
```
Esto clona la estructura del repo pero descarga solo los blobs necesarios para las rutas que activas con sparse-checkout. Un monorepo de 10GB puede quedarse en 300MB en disco.
Cuándo usarlo:
- Monorepos corporativos donde solo trabajas con una parte pequeña.
- Repos académicos o de recursos que pesan mucho y solo necesitas una carpeta.
- Setups de CI donde el build necesita solo un subdirectorio.
Cuándo no:
- Repos pequeños o medianos: la complejidad extra no compensa.
- Si vas a tocar ficheros de distintas áreas continuamente: vas a estar añadiendo rutas todo el rato.
## Combinando los tres
Un setup realista para un monorepo grande, trabajando en frontend:
```bash
# Clone mínimo con solo frontend
git clone --filter=blob:none --sparse git@github.com:empresa/mono
cd mono
git sparse-checkout init --cone
git sparse-checkout set frontend/app frontend/shared
# Un worktree aparte para atender un hotfix en backend
git sparse-checkout add backend/api # traemos el área
git worktree add ../mono-hotfix -b hotfix/api-timeout origin/main
cd ../mono-hotfix
# ... trabajo ...
# Librería común vivida como submódulo
git submodule update --init libs/ui-kit
```
Un setup así, hace cinco años, era prácticamente inviable. Hoy git lo soporta nativamente.
## Errores típicos con estas herramientas
**Submódulos olvidados al actualizar.** Tras un `git pull` que incluye cambios de submódulo, acuérdate de `git submodule update --init --recursive`. Hay un alias para hacer pull "completo":
```bash
git config --global submodule.recurse true
```
**Worktrees huérfanos.** Si borras a mano un worktree secundario sin `git worktree remove`, queda registrado. `git worktree prune` lo limpia.
**Sparse-checkout sin `--cone`.** Patrones tipo gitignore invertidos son difíciles de depurar. Quédate con `--cone` salvo que sepas exactamente qué haces.
## Fin de la serie
Con esto cerramos los 27 comandos (nueve más básicos, nueve intermedios, nueve avanzados) que cubren, en mi experiencia, el 95% de lo que uno necesita en un trabajo normal con git. Lo que queda (hooks, plumbing commands, filter-repo, git-lfs, bundles, notes) es territorio muy específico que vale la pena cuando lo necesitas y no antes.
Si quieres repasar: **[Git básico](/search?tag=git-basico)** son los cimientos, **[Git intermedio](/search?tag=git-intermedio)** es el día a día con equipo, y **[Git avanzado](/search?tag=git-avanzado)** es la caja de herramientas para cuando las cosas se ponen interesantes. Y si todavía no lo has leído, el post sobre **[git aliases](/search?q=git+aliases)** es lo que te convierte todo esto en muscle memory.
---
# PostgreSQL desde cero (III): CTEs, window functions y consultas avanzadas
URL: https://javiervalencia.net/post/postgresql-desde-cero-iii-ctes-window-functions-y-consultas-avanzadas
*Tercera entrega de la serie **[PostgreSQL desde cero a pro](/search?tag=postgresql-desde-cero)**. Tiempo de lectura estimado: 13 minutos.*
Ya sabes instalar PostgreSQL ([I](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos)) y diseñar un buen esquema con tipos y restricciones ([II](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones)). Ahora toca la parte divertida: las partes del lenguaje SQL que separan a alguien que "sabe SQL" de alguien que de verdad saca partido a PostgreSQL.
Si ya tienes algo de experiencia, partes de este post te sonarán del post [PostgreSQL: 10 consultas que todo desarrollador debería conocer](/post/postgresql-10-consultas-que-todo-desarrollador-deberia-conocer). Lo que hago aquí es sistematizar y ampliar.
## CTEs (Common Table Expressions)
Una CTE es una subquery con nombre. La sintaxis es `WITH ... AS (...)` y se puede encadenar:
```sql
WITH paid_invoices AS (
SELECT * FROM invoices WHERE status = 'paid'
),
monthly AS (
SELECT
date_trunc('month', paid_at) AS month,
sum(amount) AS total
FROM paid_invoices
GROUP BY 1
)
SELECT
month,
total,
total - lag(total) OVER (ORDER BY month) AS diff
FROM monthly
ORDER BY month;
```
Ventajas:
- **Legibilidad**: rompe consultas grandes en pasos con nombre.
- **Reutilización dentro de la consulta**: una CTE puede referenciarse varias veces.
- **Separación de lógica**: cada CTE es una unidad comprensible por sí sola.
Históricamente, PostgreSQL trataba las CTEs como **optimization fences** (materializaba el resultado intermedio). Desde la versión 12, el planner puede inline la CTE cuando es mejor, salvo que la marques como `WITH ... AS MATERIALIZED (...)` o `NOT MATERIALIZED`.
### CTEs recursivas
El nombre asusta pero la idea es simple: una CTE que se auto-referencia. La forma canónica:
```sql
WITH RECURSIVE
anchor AS (SELECT ... base_case ...),
recursive_step AS (SELECT ... FROM anchor ...)
SELECT * FROM (anchor UNION ALL recursive_step);
```
En la práctica se escribe así:
```sql
WITH RECURSIVE tree AS (
-- Caso base
SELECT id, parent_id, name, 1 AS depth
FROM categories
WHERE parent_id IS NULL
UNION ALL
-- Paso recursivo
SELECT c.id, c.parent_id, c.name, t.depth + 1
FROM categories c
JOIN tree t ON c.parent_id = t.id
)
SELECT * FROM tree ORDER BY depth, name;
```
Para cualquier estructura jerárquica (categorías, comentarios anidados, árbol de organización, grafo acíclico) esto es el patrón de libro.
### Mutating CTEs
Las CTEs pueden contener `INSERT`, `UPDATE` o `DELETE` con `RETURNING`:
```sql
WITH deleted_rows AS (
DELETE FROM expired_sessions WHERE expires_at < now() RETURNING id, user_id
)
INSERT INTO audit_log (action, entity, entity_id, user_id)
SELECT 'session_expired', 'session', id, user_id FROM deleted_rows;
```
Borrar y auditar en una sola consulta, con garantías transaccionales. Muy útil para pipelines de limpieza.
## Window functions
Las window functions calculan un valor **por fila** usando una ventana de filas relacionadas, sin colapsar el resultado como hace `GROUP BY`.
Estructura general:
```sql
función() OVER (PARTITION BY ... ORDER BY ... ROWS/RANGE ...)
```
### Funciones de ranking
```sql
SELECT
department,
employee,
salary,
row_number() OVER (PARTITION BY department ORDER BY salary DESC) AS rn,
rank() OVER (PARTITION BY department ORDER BY salary DESC) AS rk,
dense_rank() OVER (PARTITION BY department ORDER BY salary DESC) AS drk
FROM employees;
```
Diferencia clave:
- `row_number()`: números consecutivos únicos dentro de la partición.
- `rank()`: misma posición para empates, salta números (1, 2, 2, 4).
- `dense_rank()`: misma posición para empates, sin saltar (1, 2, 2, 3).
Top N por grupo, un clásico:
```sql
SELECT *
FROM (
SELECT
category_id, product_id, views,
row_number() OVER (PARTITION BY category_id ORDER BY views DESC) AS rn
FROM products
) ranked
WHERE rn <= 3;
```
### Agregaciones como ventana
Cualquier función de agregación (`sum`, `avg`, `count`, `min`, `max`, `string_agg`, `array_agg`) puede usarse como window:
```sql
SELECT
order_id,
customer_id,
total,
sum(total) OVER (PARTITION BY customer_id ORDER BY created_at
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
FROM orders;
```
### lag, lead, first_value, last_value
```sql
SELECT
day,
value,
lag(value, 1) OVER (ORDER BY day) AS prev_day,
lead(value, 1) OVER (ORDER BY day) AS next_day,
value - lag(value, 1) OVER (ORDER BY day) AS diff
FROM metrics;
```
`lag(x, n)` devuelve el valor `n` filas antes; `lead(x, n)` lo contrario. Para series temporales, cálculo de deltas y movings, son insustituibles.
### FILTER
Desde PostgreSQL 9.4, puedes filtrar dentro de una agregación sin usar `CASE`:
```sql
SELECT
department,
count(*) AS total,
count(*) FILTER (WHERE status = 'active') AS active,
count(*) FILTER (WHERE hired_at > now() - '1 year'::interval) AS new,
avg(salary) FILTER (WHERE status = 'active') AS avg_active_salary
FROM employees
GROUP BY department;
```
Es legible, rápido y hace la misma pasada por los datos.
## LATERAL joins
`LATERAL` permite que una subquery a la derecha de un join **se referencie a columnas de la izquierda**. Es un "para cada fila de la izquierda, ejecuta esta subquery".
Ejemplo: últimos 3 pedidos de cada cliente, todo en una consulta:
```sql
SELECT
c.id,
c.name,
o.created_at,
o.total
FROM customers c
LEFT JOIN LATERAL (
SELECT created_at, total
FROM orders
WHERE customer_id = c.id
ORDER BY created_at DESC
LIMIT 3
) o ON true;
```
Equivalente funcional al "top N por grupo" que antes hacíamos con window + filtro, a veces más eficiente, a veces menos. Mide.
## UPSERT con ON CONFLICT
Insertar-o-actualizar en una sola instrucción atómica:
```sql
INSERT INTO page_views (path, day, views)
VALUES ('/home', CURRENT_DATE, 1)
ON CONFLICT (path, day)
DO UPDATE SET views = page_views.views + EXCLUDED.views;
```
- `ON CONFLICT (...)` declara el conflicto (una restricción única).
- `DO UPDATE` indica qué hacer si hay conflicto.
- `EXCLUDED` representa los valores que **intentabas** insertar.
- `DO NOTHING` es la variante "INSERT IGNORE".
Esto es fundamental para contadores incrementales, estado denormalizado y cualquier flujo idempotente.
## RETURNING
Cualquier `INSERT`, `UPDATE` o `DELETE` puede devolver filas:
```sql
UPDATE orders
SET status = 'shipped', shipped_at = now()
WHERE status = 'paid' AND paid_at < now() - '1 hour'::interval
RETURNING id, customer_id;
```
Una sola *round trip* para cambiar estado y recuperar los IDs afectados. Ideal para pipelines de procesamiento en lote.
## Manipulación de JSONB
Recuperación y filtrado ya los vimos en la [entrega II](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones). Para modificar JSONB:
```sql
-- Añadir / sobreescribir clave
UPDATE events
SET data = data || '{"processed": true}'::jsonb
WHERE id = 42;
-- Eliminar clave
UPDATE events SET data = data - 'tmp' WHERE ...;
-- Ruta profunda con jsonb_set
UPDATE events
SET data = jsonb_set(data, '{user,plan}', '"premium"', true)
WHERE id = 42;
```
Desde PostgreSQL 16, el soporte de `JSON_TABLE` y expresiones `jsonpath` SQL/JSON es enorme. Para consultas muy específicas, míratelo.
## Generate series
Una función que genera filas artificiales:
```sql
SELECT * FROM generate_series(1, 10);
SELECT generate_series(
date_trunc('month', now()) - interval '11 months',
date_trunc('month', now()),
interval '1 month'
) AS month;
```
Muy útil para rellenar huecos en series temporales con `LEFT JOIN`:
```sql
SELECT
gs.day,
coalesce(count(v.id), 0) AS views
FROM generate_series(
CURRENT_DATE - 30,
CURRENT_DATE,
interval '1 day'
) AS gs(day)
LEFT JOIN page_views v ON v.created_at::date = gs.day
GROUP BY gs.day
ORDER BY gs.day;
```
Si vienes de la [entrega III de ClickHouse](/post/clickhouse-desde-cero-iii-consultas-analiticas-en-profundidad), esto es el equivalente a `WITH FILL` pero más verboso.
## DISTINCT ON
Una extensión de PostgreSQL muy útil:
```sql
SELECT DISTINCT ON (user_id)
user_id, event_type, created_at
FROM events
ORDER BY user_id, created_at DESC;
```
Devuelve "la primera fila por `user_id` según el orden dado". Equivalente a un window + filtro, pero mucho más conciso para el caso de "la última fila de cada grupo".
## Full text search
PostgreSQL incluye búsqueda full-text integrada:
```sql
-- Columna auto-generada con el vector de búsqueda
ALTER TABLE posts
ADD COLUMN search tsvector GENERATED ALWAYS AS (
to_tsvector('spanish', coalesce(title, '') || ' ' || coalesce(body, ''))
) STORED;
CREATE INDEX posts_search_idx ON posts USING GIN (search);
SELECT id, title, ts_rank(search, q) AS rank
FROM posts, plainto_tsquery('spanish', 'postgresql rendimiento') AS q
WHERE search @@ q
ORDER BY rank DESC
LIMIT 10;
```
No reemplaza a Elasticsearch o Typesense para búsquedas muy sofisticadas, pero para un blog, un catálogo mediano o una documentación es más que suficiente y no añade infraestructura.
## Ejemplo completo
Funnel de conversión calculado en una sola consulta:
```sql
WITH sessions AS (
SELECT
session_id,
user_id,
min(created_at) FILTER (WHERE event = 'visit') AS visited_at,
min(created_at) FILTER (WHERE event = 'signup') AS signed_up_at,
min(created_at) FILTER (WHERE event = 'purchase') AS purchased_at
FROM events
WHERE created_at >= now() - '30 days'::interval
GROUP BY session_id, user_id
),
funnel AS (
SELECT
count(*) AS visits,
count(*) FILTER (WHERE signed_up_at IS NOT NULL) AS signups,
count(*) FILTER (WHERE purchased_at IS NOT NULL) AS purchases
FROM sessions
WHERE visited_at IS NOT NULL
)
SELECT
visits,
signups,
purchases,
round(100.0 * signups / nullif(visits, 0), 2) AS signup_rate,
round(100.0 * purchases / nullif(signups, 0), 2) AS conversion_rate
FROM funnel;
```
CTEs, `FILTER`, agregaciones condicionales y `nullif` para evitar divisiones por cero. Todo en una consulta declarativa que se lee de arriba a abajo.
## Por dónde seguir
- **[IV: índices, EXPLAIN y rendimiento](/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento)** — cómo averiguar por qué esta consulta que acabamos de escribir tarda tres segundos.
- **[V: replicación, backups y producción](/post/postgresql-desde-cero-v-replicacion-backups-y-produccion)** — último tramo de la serie.
Si te perdiste algo: [I](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos) o [II](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones).
---
# Git avanzado II: detective — bisect, blame y reflog
URL: https://javiervalencia.net/post/git-avanzado-ii-detective
*Segunda entrega de la serie **[Git avanzado](/search?tag=git-avanzado)**. La anterior fue sobre **[reescribir la historia con rebase, cherry-pick y amend](/search?tag=git-avanzado)**. Tiempo de lectura estimado: 5 minutos.*
La parte menos glamurosa pero más salvavidas de git: los comandos para investigar. No los escribes a diario, pero el día que los necesitas, te ahorran horas. Tres: `git bisect` para cazar el commit que introdujo un bug, `git blame` para saber quién escribió qué línea y cuándo, y `git reflog` para recuperar lo que parecía perdido para siempre.
## `git bisect`: búsqueda binaria de bugs
El escenario: ayer funcionaba, hoy no. Entre ayer y hoy hay treinta commits. ¿Cuál de ellos lo rompió?
`git bisect` automatiza la búsqueda binaria por tu historial: tú le das un commit "bueno" (pasado) y uno "malo" (actual), y git va saltando a puntos intermedios para que los pruebes. En vez de probar treinta, pruebas cinco.
```bash
git bisect start
git bisect bad # HEAD está roto
git bisect good v1.2.0 # v1.2.0 estaba bien
```
Git calcula el commit del medio y hace checkout. Tú compruebas si ese commit está roto o no:
```bash
# Pruebo que el bug está aquí o no
git bisect bad # si el bug sigue en este commit
git bisect good # si aquí aún funcionaba
```
Git recalcula, salta al siguiente punto del medio, y repite. Al final te dice: "el primer commit malo fue abc123". Cuando termines:
```bash
git bisect reset
```
Vuelves a donde estabas antes de empezar.
La versión automática es aún mejor. Si tienes un comando o script que te dice si el bug está o no (exit code 0 = bueno, distinto de 0 = malo):
```bash
git bisect start HEAD v1.2.0
git bisect run ./test-del-bug.sh
```
Te vas a tomar un café. Cuando vuelves, git te dice en qué commit exacto se rompió, con su autor, fecha y diff.
Bisect bien hecho es magia: un bug que tardarías dos horas en localizar leyendo código lo encuentras en diez minutos. El truco es tener una forma automatizable de probar "está roto o no". Un test, un `curl`, un script que compila y ejecuta. Cuanto más específico el test, mejor el diagnóstico.
Consejos:
- **Elige un "good" viejo de verdad.** Si el bug entró hace 200 commits, buscar entre los últimos 20 no lo va a encontrar.
- **Excluye cambios irrelevantes con `git bisect skip`** si un commit intermedio no compila o no se puede probar. Git lo ignora y sigue.
- **Anota el hash y el resumen antes de hacer reset**, que luego se te olvida.
## `git blame`: quién escribió esta línea
`git blame` te dice, para cada línea de un fichero, cuál fue el último commit que la modificó y quién era el autor.
```bash
git blame src/handler.go
```
Output típico:
```
abc1234 (Alicia 2025-11-03 14:22:11 +0100 42) func handleLogin(w http.ResponseWriter, r *http.Request) {
def5678 (Bruno 2026-01-15 10:05:33 +0100 43) email := r.FormValue("email")
9012ghi (Alicia 2025-11-03 14:22:11 +0100 44) if email == "" {
```
Cada línea te muestra: hash del commit, autor, fecha, número de línea, contenido.
Variantes útiles:
```bash
git blame -L 40,60 src/handler.go # solo líneas 40 a 60
git blame -L :handleLogin src/handler.go # solo la función handleLogin
git blame -w src/handler.go # ignora cambios de whitespace
git blame -M src/handler.go # detecta movimientos dentro del fichero
git blame -C src/handler.go # detecta copias desde otros ficheros
```
`-w` es imprescindible cuando alguien reformateó el fichero con un linter y ahora `blame` te señala a esa persona en cada línea. Con `-w`, git ignora ese commit de formateo y te muestra el autor original.
`-M` y `-C` son más sofisticados: rastrean cuándo una línea vino de otro lado (otra función, otro fichero). En código refactorizado mucho, estos flags cambian totalmente el resultado.
**Importante**: `git blame` no es para culpar a nadie. El nombre es histórico (y muy americano). En la práctica es "¿de dónde viene esta línea?" para **entender**, no para señalar. En los repos que uso, el primer uso de `blame` suele ser "¿en qué commit se introdujo este comportamiento?" para leer ese commit y su contexto.
Para ver la historia completa de una línea, no solo el último cambio:
```bash
git log -L 42,42:src/handler.go
```
Te muestra cada vez que la línea 42 cambió, con el diff completo. Súper útil para líneas que se han tocado muchas veces.
## `git reflog`: la máquina del tiempo
Esto es el seguro de vida de git. `reflog` es un registro de cada movimiento que ha hecho `HEAD`: cada cambio de rama, cada commit, cada reset, cada merge, todo. Está solo en tu repo local (no se pushea) y guarda unos 30-90 días de historial por defecto.
```bash
git reflog
```
Output típico:
```
abc1234 HEAD@{0}: commit: Añadir validación
def5678 HEAD@{1}: reset: moving to HEAD~1
9012ghi HEAD@{2}: commit: WIP
345jklm HEAD@{3}: checkout: moving from main to feature
```
Cada línea es "dónde estuvo HEAD en un momento". `HEAD@{N}` significa "dónde estaba HEAD hace N movimientos".
Cómo te salva el reflog:
**Hiciste `git reset --hard` y borraste trabajo:**
```bash
git reflog # busca el hash de antes del reset
git reset --hard abc1234 # (o HEAD@{1}) vuelves
```
**Hiciste un rebase interactivo y te lo cargaste todo:**
```bash
git reflog | head -20 # encuentra el commit previo al rebase
git reset --hard HEAD@{5} # vuelves al estado pre-rebase
```
**Borraste una rama sin querer:**
```bash
git reflog # encuentra el último commit de la rama
git branch recuperada abc1234 # creas una rama nueva en ese commit
```
Esta última es oro: incluso cuando `git branch -D mi-rama` parece borrar, el commit al que apuntaba sigue accesible por reflog durante un tiempo. Si lo pillas rápido, lo recuperas.
Una variante del reflog es por rama:
```bash
git reflog main
```
Te muestra la historia de movimientos de la rama `main` específicamente. Útil cuando un reset afectó a una rama concreta.
**Limitaciones**:
- Es local. Si clonas el repo en otra máquina, el reflog de origen no viene contigo.
- Tiene caducidad. Los commits inalcanzables (sin referencia) acaban siendo eliminados por `git gc`. Si algo pasó hace tres meses y no te diste cuenta, quizá ya no esté.
- No es un backup. Úsalo como red de seguridad para errores recientes, no como política de backup.
## Un caso real que resuelven estos tres comandos
"Un test que ayer pasaba hoy falla. No sé qué he tocado."
```bash
# 1. ¿Qué ha cambiado entre ayer y hoy?
git log --oneline --since="1 day ago"
# 2. Si hay muchos commits, bisect:
git bisect start HEAD @{yesterday}
git bisect run ./test.sh
# → git identifica el commit culpable
# 3. Miro el commit con blame para entender:
git show abc1234
git blame -L 42,60 src/fichero-modificado.go
# 4. Si resuelvo con reset y me pasé, reflog me devuelve:
git reflog
git reset --hard HEAD@{3}
```
Cuatro comandos, diez minutos, problema diagnosticado. Sin bisect y blame, la misma investigación son dos horas de leer diffs.
## Errores típicos en modo detective
**Saltarse bisect por pereza.** "Voy a leer los commits a ver si veo algo". Cuando hay más de cinco commits, bisect es siempre más rápido.
**Culpar de verdad con blame.** El objetivo es entender cómo llegó allí el código, no recriminar. Si el commit que introdujo algo fue hace cinco años, las circunstancias eran otras.
**Asumir que el reflog es eterno.** Si algo importante se ha perdido, recupéralo ya. Cada operación nueva en el repo hace correr el tiempo del reflog.
## Lo que viene
Con bisect, blame y reflog puedes investigar prácticamente cualquier cosa que haya pasado en un repo. Para cerrar la serie avanzada, en la **[última entrega](/search?tag=git-avanzado)** toca el equipamiento para proyectos grandes: `git worktree` para tener varias ramas a la vez sin reclonar, `git submodule` para gestionar dependencias como subrepos, y `git sparse-checkout` para trabajar con monorepos sin cargar todo el peso.
---
# Git avanzado I: reescribir la historia
URL: https://javiervalencia.net/post/git-avanzado-i-reescribir-la-historia
*Primera entrega de la serie **[Git avanzado](/search?tag=git-avanzado)**. Si vienes saltado desde el nivel **[intermedio](/search?tag=git-intermedio)** o **[básico](/search?tag=git-basico)**, aquí entramos en el terreno donde git deja de ser sistema de control de versiones y empieza a ser editor de historia. Tiempo de lectura estimado: 5 minutos.*
Los tres comandos de hoy permiten modificar commits que ya existen: combinarlos, reordenarlos, cambiarles el mensaje, sacarlos de una rama y meterlos en otra. Son potentes y razonablemente peligrosos: si la historia que estás reescribiendo ya la han visto otros, les vas a desordenar el mundo. La regla general, antes de empezar: **reescribe historia local todo lo que quieras, reescribe historia publicada solo con acuerdo explícito del equipo**.
## `git commit --amend`: editar el último commit
`--amend` no crea un commit nuevo: modifica el anterior. Se usa principalmente para dos cosas.
**Cambiar el mensaje del último commit:**
```bash
git commit --amend -m "Mensaje corregido"
```
O sin `-m`, que te abre el editor con el mensaje actual para que lo edites.
**Añadir algo que se te olvidó:**
```bash
git add fichero-que-olvide.go
git commit --amend --no-edit
```
`--no-edit` conserva el mensaje existente. El fichero se añade al commit anterior como si siempre hubiera estado ahí. Muy cómodo cuando haces commit y te das cuenta de que se te olvidó algo trivial (un archivo generado, una línea de docs, un test).
Cuidado: `--amend` crea un commit **nuevo** por debajo (con nuevo hash), aunque conserva el mismo mensaje y la misma fecha del autor. Si el commit anterior ya estaba pusheado, tras el amend tu rama y la remota divergen. Tendrás que `git push --force-with-lease`. En una rama solo tuya (feature branch abierta solo por ti), no pasa nada. En main o en una rama compartida, problema.
## `git rebase`: mover commits a otra base
`git rebase` toma un conjunto de commits y los reaplica sobre otro punto. Dicho de otro modo: cambia la "base" desde la que parte una rama.
El caso más típico: estás en una rama de feature, `main` ha avanzado mientras trabajabas, y quieres traer tu rama al día sin un merge commit.
```bash
git switch feature/login
git fetch origin
git rebase origin/main
```
Resultado: tus commits se despegan, git aplica los commits nuevos de `origin/main` y luego vuelve a aplicar los tuyos encima. La historia queda lineal, como si hubieras partido de `origin/main` actual desde el principio.
Si hay conflictos durante el rebase, git se para en cada commit que conflicte, te deja marcar los conflictos, y esperas: tras resolver, `git add` y `git rebase --continue`. Si te arrepientes: `git rebase --abort` y vuelves al estado anterior.
La otra modalidad es el rebase interactivo, que es donde rebase se convierte en editor de historia:
```bash
git rebase -i HEAD~5
```
Te abre un editor con los últimos 5 commits y un verbo al lado de cada uno:
```
pick abc1234 Añadir formulario de login
pick def5678 Arreglar typo
pick 9012ghi Validación de email
pick 345jklm WIP trabajando
pick nop6789 Test del formulario
```
Cambias los verbos:
- `pick`: mantener tal cual
- `reword` (r): mantener pero editar el mensaje
- `squash` (s): fusionar con el commit anterior, combinando mensajes
- `fixup` (f): fusionar con el anterior, descartando el mensaje
- `drop` (d): borrar el commit
- `edit` (e): parar en ese commit para modificarlo
Y puedes reordenar las líneas para reordenar los commits. Al guardar y cerrar el editor, git aplica los cambios.
El resultado típico tras un rebase interactivo es una historia limpia: en vez de cinco commits con "WIP", "otra cosa", "ahora sí", tienes dos commits bien titulados que cuentan lo que pasó. Esto es oro cuando alguien hace code review: lee los commits uno a uno y entiende el razonamiento.
**Rebase en ramas compartidas**: solo si has acordado con el equipo que esa rama se reescribe (es común en branches de PR con historial limpio requerido). Para mergear tras rebase propio: `git push --force-with-lease`, nunca `--force` a secas.
## `git cherry-pick`: traer un commit de otra rama
`cherry-pick` coge un commit concreto y lo aplica sobre la rama actual, creando un commit nuevo (mismo contenido, hash distinto).
```bash
git cherry-pick abc123 # un commit concreto
git cherry-pick abc123 def456 # varios
git cherry-pick abc123..def456 # un rango (exclusivo el primero)
git cherry-pick abc123^..def456 # un rango (inclusivo)
```
Cuándo se usa:
**Un fix que hiciste en develop y te hace falta en main ya.** En vez de esperar al próximo merge completo: `git switch main && git cherry-pick abc123`.
**Backport de un fix a una rama de release antigua.** Si mantienes `release/v1` y arreglaste un bug en `main`, cherry-pick ese commit a la rama de release.
**Rescatar commits de una rama que vas a tirar.** Si una rama de experimento tiene dos commits que merecen la pena pero el resto es basura, cherry-pick los dos buenos a otra rama y tira la experimental.
Si hay conflictos, igual que con rebase: resolver, `git add`, `git cherry-pick --continue`. O `--abort` para cancelar.
Flags útiles:
```bash
git cherry-pick -n abc123 # aplica los cambios pero no hace el commit
git cherry-pick -x abc123 # añade al mensaje "(cherry picked from abc123)"
```
El `-x` es útil cuando mantienes varias ramas de release: en el mensaje queda trazabilidad explícita de dónde salió el fix originalmente.
Peligros de cherry-pick:
- Si el commit original modifica luego (rebase, amend), tendrás dos versiones ligeramente distintas del mismo cambio. Trazar eso a mano es un dolor.
- Cherry-pick en serie (traer cincuenta commits uno a uno) es mejor hacerlo con merge o rebase. Cherry-pick es para puntuales.
## Un ejemplo real del día a día
Tienes una rama `feature/carrito`. Llevas seis commits:
```
pick a1 Añadir modelo de Carrito
pick a2 WIP
pick a3 arreglar bug lint
pick a4 Añadir servicio de cálculo
pick a5 Tests servicio cálculo
pick a6 typo
```
Antes del PR, haces rebase interactivo (`git rebase -i main`):
```
pick a1 Añadir modelo de Carrito
fixup a2
fixup a3
pick a4 Añadir servicio de cálculo
fixup a6
pick a5 Tests servicio cálculo
```
Guardas, cierras. Resultado: tres commits limpios:
```
Añadir modelo de Carrito
Añadir servicio de cálculo
Tests servicio cálculo
```
Cada commit es una unidad autocontenida que hace una cosa, testeada, revisable. El reviewer te lo agradece; tu yo de dentro de tres meses buscando en `git log`, también.
## Errores típicos reescribiendo historia
**Hacer force push en main.** Borras trabajo de todos. `--force-with-lease` al menos detecta que había algo nuevo, pero aun así, reescribir main es casi nunca una buena idea.
**Rebase interactivo sin antes hacer backup de la rama.** Si el rebase se te va de las manos, `git reflog` te salva, pero es más tranquilizador hacer antes `git branch backup-feature-carrito` y, si todo explota, `git reset --hard backup-feature-carrito`.
**Cherry-pick sin marcar el origen.** En tres semanas nadie se acordará de por qué existe ese commit. Si vas a cherry-pick entre ramas de release, usa `-x`.
**Amend + force push sin avisar.** Si alguien más tiene esa rama pulleada, al hacer force push les rompes su copia local. Coordinación mínima.
## Lo que viene
Con amend, rebase y cherry-pick puedes dejar la historia de un repo como quieras. Pero tener herramientas para cambiar la historia no sirve de mucho si luego no sabes leerla cuando algo falla. En la **[siguiente entrega](/search?tag=git-avanzado)**, los comandos de investigación: `git bisect`, `git blame` y `git reflog`. Las tres armas para responder a "¿qué demonios pasó aquí?".
---
# PostgreSQL desde cero (II): tipos, restricciones y relaciones
URL: https://javiervalencia.net/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones
*Segunda entrega de la serie **[PostgreSQL desde cero a pro](/search?tag=postgresql-desde-cero)**. Tiempo de lectura estimado: 11 minutos.*
En la [entrega anterior](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos) levantamos PostgreSQL, creamos una base de datos y metimos las primeras filas. En este post nos centramos en lo que convierte PostgreSQL en una base de datos seria: **tipos ricos** y **restricciones** que garantizan que los datos sean correctos desde la base de datos, no desde la capa de aplicación.
El principio subyacente: si puedes hacer que un dato imposible sea imposible de insertar, **hazlo**. Las validaciones en la aplicación están muy bien; las restricciones en la base de datos son la última línea de defensa y la única que no se salta con un backfill manual por psql a las tres de la mañana.
## El sistema de tipos
PostgreSQL tiene uno de los sistemas de tipos más ricos entre las bases de datos relacionales.
### Enteros, decimales, floats
```sql
SMALLINT -- 2 bytes, ±32k
INTEGER -- 4 bytes, ±2.100M
BIGINT -- 8 bytes, ±9 trillones
NUMERIC(p, s) -- precisión arbitraria. Para dinero: SIEMPRE
REAL -- 4 bytes float
DOUBLE PRECISION -- 8 bytes float
```
Regla universal: **dinero = `NUMERIC`**. Nunca `REAL` ni `DOUBLE PRECISION` para importes. El redondeo de los float te hace llorar el día de la auditoría.
### Texto
```sql
TEXT -- variable, sin límite de longitud
VARCHAR(n) -- variable, con longitud máxima
CHAR(n) -- fijo (padding con espacios)
```
Al contrario que en otras bases de datos, en PostgreSQL **`TEXT` y `VARCHAR` tienen el mismo rendimiento**. No hay razón para usar `VARCHAR(n)` salvo que quieras imponer un límite. `CHAR(n)` casi nunca es lo que quieres.
### Fechas y tiempos
```sql
DATE
TIME -- solo hora, sin fecha
TIMESTAMP -- fecha y hora, SIN zona horaria
TIMESTAMPTZ -- fecha y hora, CON zona horaria
INTERVAL
```
Usa **siempre `TIMESTAMPTZ`** a menos que sepas exactamente por qué no. `TIMESTAMPTZ` guarda el instante en UTC y lo presenta en el huso horario de la sesión. `TIMESTAMP` te dejará clavado en un huso sin más contexto y ese bug aparecerá cuando viajes a otro país.
### UUID
```sql
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
...
);
```
Los UUID como PK son útiles cuando generas IDs en el cliente o quieres evitar adivinar enumeraciones. Son más grandes que los enteros (16 bytes vs 8) y se fragmentan más en los índices: para una clave primaria muy caliente, sigue siendo mejor `BIGINT` con secuencia.
### Tipos propios: enums
```sql
CREATE TYPE post_status AS ENUM ('draft', 'published', 'archived');
ALTER TABLE posts ADD COLUMN status post_status NOT NULL DEFAULT 'draft';
```
Los enums son rápidos y ocupan poco. El contra: añadir un valor requiere `ALTER TYPE`, que en versiones antiguas podía ser molesto. En PostgreSQL moderno es trivial:
```sql
ALTER TYPE post_status ADD VALUE 'scheduled' AFTER 'draft';
```
Alternativa idiomática: una columna `TEXT` con un `CHECK` que limita los valores. Más flexible que el enum, más lento por fila (muy poco).
### Arrays
```sql
CREATE TABLE posts (
...
tags TEXT[]
);
INSERT INTO posts (..., tags) VALUES (..., ARRAY['postgresql', 'tutorial']);
SELECT * FROM posts WHERE 'postgresql' = ANY(tags);
SELECT * FROM posts WHERE tags @> ARRAY['postgresql', 'tutorial'];
SELECT tag, count(*) FROM posts, unnest(tags) AS tag GROUP BY tag;
```
Los arrays son geniales para datos que no merecen una tabla dedicada: tags, identificadores secundarios, opciones de una config simple. Si estás manipulando un array con mucha frecuencia o necesitas atributos por elemento, es señal de que toca modelo relacional normal.
### JSON y JSONB
PostgreSQL tiene dos tipos para JSON:
- `JSON`: almacena el texto tal cual. Preserva formato y duplicados de claves.
- `JSONB`: parseado a estructura binaria. Más rápido para consultar, permite índices GIN.
**Usa `JSONB` siempre**, salvo que necesites preservar el formato exacto (casi nunca).
```sql
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
type TEXT NOT NULL,
data JSONB NOT NULL
);
INSERT INTO events (type, data) VALUES
('signup', '{"email":"a@b.com","plan":"free","referrer":"google"}');
-- Acceso
SELECT data->>'email', data->'plan' FROM events;
SELECT * FROM events WHERE data @> '{"plan":"free"}';
SELECT * FROM events WHERE data ? 'referrer';
-- Índice GIN para consultas de contención
CREATE INDEX events_data_gin ON events USING GIN (data jsonb_path_ops);
```
Operadores clave:
- `->` devuelve JSONB
- `->>` devuelve TEXT
- `@>` "contiene"
- `?` "tiene la clave"
- `#>` ruta (igual que `->` encadenado)
`JSONB` no reemplaza al modelo relacional: es para lo que de verdad es semi-estructurado (payloads de webhooks, propiedades de evento de analítica, configuraciones flexibles). Si vas a consultar siempre los mismos cinco campos, crea columnas.
## Restricciones
### NOT NULL
Obligatorio pensar en qué columnas pueden ser nulas y cuáles no. `NOT NULL` es la restricción más fundamental y la más olvidada.
```sql
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL,
name TEXT NOT NULL
);
```
### UNIQUE
```sql
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);
-- Único compuesto
CREATE TABLE memberships (
user_id BIGINT NOT NULL,
group_id BIGINT NOT NULL,
UNIQUE (user_id, group_id)
);
-- Único parcial (solo para filas activas)
CREATE UNIQUE INDEX users_email_active_unique
ON users(lower(email))
WHERE deleted_at IS NULL;
```
El índice único **parcial** es una herramienta infravalorada: permite tener "único entre los activos" sin sacrificar historicidad.
### PRIMARY KEY vs IDENTITY
`BIGSERIAL` sigue siendo válido, pero el estándar SQL moderno es:
```sql
CREATE TABLE authors (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
...
);
```
Diferencias prácticas:
- `GENERATED ALWAYS`: el cliente no puede insertar un valor explícito (a menos que use `OVERRIDING SYSTEM VALUE`).
- `GENERATED BY DEFAULT`: se genera si no lo das, pero puedes darlo.
- Encaja mejor con migraciones entre SGBDs.
Si empiezas un proyecto hoy, usa `IDENTITY`. Si mantienes uno viejo, `BIGSERIAL` sigue funcionando perfectamente.
### CHECK
Cualquier expresión booleana vale como restricción:
```sql
CREATE TABLE products (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC(12,2) NOT NULL CHECK (price >= 0),
sku TEXT NOT NULL CHECK (sku ~ '^[A-Z0-9-]{4,20}$'),
discount NUMERIC(4,2) CHECK (discount IS NULL OR (discount >= 0 AND discount <= 1))
);
```
Los `CHECK` son rápidos, se ejecutan en inserts/updates y sobreviven a cualquier cliente buggy. Úsalos sin miedo.
### Foreign keys
```sql
CREATE TABLE posts (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
author_id BIGINT NOT NULL REFERENCES authors(id) ON DELETE RESTRICT,
...
);
```
Opciones en `ON DELETE` y `ON UPDATE`:
- `NO ACTION` (default): error si quedan referencias.
- `RESTRICT`: igual que NO ACTION pero se evalúa inmediatamente.
- `CASCADE`: propaga el borrado/update.
- `SET NULL`: pone NULL en la referencia.
- `SET DEFAULT`: pone el default de la columna.
`CASCADE` es cómodo pero peligroso: un borrado accidental puede vaciar media base de datos. Yo uso `RESTRICT` salvo que el modelo de dominio claramente pida cascada (por ejemplo, "al borrar un pedido, sus líneas").
### Exclusion constraints
El pariente menos conocido y más potente. Impide que dos filas "se solapen" según cualquier operador:
```sql
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE bookings (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
room_id BIGINT NOT NULL,
period TSTZRANGE NOT NULL,
EXCLUDE USING GIST (
room_id WITH =,
period WITH &&
)
);
```
Traducción: no puede haber dos reservas de la misma habitación cuyos periodos se solapen. Imposible. Sin triggers, sin locks manuales, sin código de aplicación defensivo. Esto es PostgreSQL luciéndose.
## Relaciones: uno a muchos, muchos a muchos
Uno a muchos: una foreign key en el lado "muchos".
```sql
authors 1 ----< N posts
```
```sql
CREATE TABLE authors (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
...
);
CREATE TABLE posts (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
author_id BIGINT NOT NULL REFERENCES authors(id),
...
);
```
Muchos a muchos: una tabla de unión con dos FKs.
```sql
posts N >----< M tags (vía post_tags)
```
```sql
CREATE TABLE tags (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
CREATE TABLE post_tags (
post_id BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
tag_id BIGINT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (post_id, tag_id)
);
```
La tabla de unión con PK compuesta da unicidad y el índice necesario para consultas en ambos sentidos.
## Migraciones seguras
PostgreSQL es transaccional también para DDL: `CREATE TABLE`, `ALTER TABLE`, etc. pueden envolverse en `BEGIN ... COMMIT`. Esto significa que una migración que falla a mitad **no deja el esquema a medio aplicar**. Es una de sus mejores cualidades.
Algunas operaciones, sin embargo, toman **locks exclusivos** en la tabla que pueden bloquear tráfico:
- `ALTER TABLE ... ADD COLUMN col TYPE NOT NULL DEFAULT 'x'` en versiones antiguas reescribía la tabla.
- `CREATE INDEX` bloquea escrituras hasta que termina.
- `ALTER TYPE` con cambios de representación reescribe datos.
Alternativas seguras:
```sql
CREATE INDEX CONCURRENTLY posts_slug_idx ON posts(slug);
```
`CONCURRENTLY` construye el índice sin bloquear escrituras, a cambio de ser más lento y no poder ejecutarse dentro de una transacción. Las herramientas modernas de migración (Flyway, sqitch, la CLI de tu ORM) suelen tener soporte explícito.
Para añadir columnas nuevas con NOT NULL en tablas grandes, el patrón moderno es:
1. Añadir columna **sin** NOT NULL.
2. Hacer backfill por lotes.
3. Añadir el NOT NULL (validación barata si no hay nulos).
## Un esquema completo
Uniendo piezas de los dos posts:
```sql
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS citext;
CREATE TYPE post_status AS ENUM ('draft', 'published', 'archived');
CREATE TABLE authors (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email CITEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE posts (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
author_id BIGINT NOT NULL REFERENCES authors(id) ON DELETE RESTRICT,
title TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
body TEXT NOT NULL,
status post_status NOT NULL DEFAULT 'draft',
metadata JSONB NOT NULL DEFAULT '{}',
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CHECK (status != 'published' OR published_at IS NOT NULL)
);
CREATE TABLE tags (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name CITEXT NOT NULL UNIQUE
);
CREATE TABLE post_tags (
post_id BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
tag_id BIGINT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (post_id, tag_id)
);
CREATE INDEX posts_published_at_idx ON posts(published_at DESC)
WHERE status = 'published';
```
Dos detalles nuevos:
- `CITEXT` (extensión): texto case-insensitive. Para emails y nombres de tag, ahorra `lower()` por todos lados.
- El `CHECK` en `posts` impone invariante: si el estado es `published`, `published_at` no puede ser NULL.
## Por dónde seguir
- **[III: CTEs, window functions y consultas avanzadas](/post/postgresql-desde-cero-iii-ctes-window-functions-y-consultas-avanzadas)** — ya tienes buenos datos, ahora toca consultarlos como un adulto.
- **[IV: índices, EXPLAIN y rendimiento](/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento)** — cuando el modelo crece y las consultas ya no son instantáneas.
- **[V: replicación, backups y producción](/post/postgresql-desde-cero-v-replicacion-backups-y-produccion)** — operar PostgreSQL sin perder datos ni sueño.
¿Llegas tarde a la serie? [I: instalación, psql y primeros pasos](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos).
---
# Git intermedio III: stash, tags y fetch
URL: https://javiervalencia.net/post/git-intermedio-iii-stash-tags-y-fetch
*Tercera entrega de la serie **[Git intermedio](/search?tag=git-intermedio)**. Las anteriores: **[ramas](/search?tag=git-intermedio)** y **[deshacer con cabeza](/search?tag=git-intermedio)**. Tiempo de lectura estimado: 5 minutos.*
Para cerrar el nivel intermedio, tres comandos que resuelven situaciones muy concretas pero frecuentes: guardar trabajo en curso cuando algo urgente te interrumpe (`git stash`), marcar un punto importante de la historia (`git tag`) y mirar el estado del remoto sin traerte nada (`git fetch`). No son glamurosos, pero hacen el día a día más tranquilo.
## `git stash`: guardar trabajo a medias
El escenario: estás en medio de algo, con cambios en el working tree que aún no quieres commitear, y alguien te pide una revisión urgente en otra rama. No quieres hacer un commit a medias solo para cambiar de rama. `git stash` es el comando.
```bash
git stash # guarda cambios y vuelve a HEAD limpio
git stash push -m "WIP login" # igual, con mensaje descriptivo
git stash list # lista los stashes guardados
git stash pop # restaura el último stash y lo borra
git stash apply # restaura sin borrar el stash
git stash drop # borra un stash
git stash show -p stash@{1} # diff del stash
```
Flujo típico:
```bash
git stash -m "Formulario login a medias"
git switch rama-urgente
# ... hago el fix, commit, push, revisión, merge ...
git switch -
git stash pop
```
Detalles importantes:
- **Por defecto, `git stash` no stashea ficheros untracked**. Si tienes ficheros nuevos sin `git add`, no entran. Flag: `git stash -u` (untracked).
- **Los conflictos en pop son reales**. Si has cambiado cosas que chocan con lo stasheado, al hacer pop te aparecen conflictos como en un merge. Resuelves, `git add`, y el stash ya está aplicado (se puede borrar con `git stash drop` si usaste apply, o se borró solo si usaste pop).
- **Stash es local**: no sale de tu máquina, nadie más lo ve. No es para colaborar.
Mi recomendación: usa stash para interrupciones cortas, no como "almacén de trabajo". Si algo va a vivir más de un día, hazlo commit en una rama; los stashes se olvidan fácilmente y cuando acumulas quince te pierdes.
Variante muy útil:
```bash
git stash --keep-index
```
Stashea solo lo que no está en el index. Útil para el flujo: "hago add de lo que voy a commitear, stasheo el resto, corro tests con solo lo stageado, commiteo si pasan".
## `git tag`: marcar puntos importantes
Los tags son punteros a commits, igual que las ramas, pero con una diferencia clave: no se mueven. Una rama avanza con cada commit nuevo; un tag se queda donde estaba. Se usan típicamente para marcar versiones: `v1.0.0`, `v1.2.3`, `release-2026-04`.
Hay dos tipos:
```bash
git tag v1.0.0 # lightweight tag
git tag -a v1.0.0 -m "Release 1.0" # annotated tag
```
Los **annotated** guardan autor, fecha, mensaje y firma opcional. Los **lightweight** son solo un puntero con un nombre. Para releases públicas se usan annotated; para tags locales rápidos, lightweight.
Operaciones básicas:
```bash
git tag # lista todos los tags
git tag -l "v1.*" # lista con patrón
git tag -a v1.2.0 abc123 # etiqueta un commit concreto
git tag -d v1.0.0 # borra un tag local
git push origin v1.0.0 # pushea UN tag
git push origin --tags # pushea todos los tags
git push origin :refs/tags/v1.0.0 # borra un tag en el remoto
```
**Los tags no se pushean con `git push` normal**. Por defecto git no los sube. Tienes que hacerlo explícitamente. Esto evita subir tags de prueba por error, pero también hace que mucha gente se olvide. Si haces release, acuérdate.
Ver qué hay en un tag:
```bash
git show v1.0.0
```
Te enseña el mensaje del tag (si es annotated), el commit que apunta y el diff de ese commit.
Los tags se usan también en workflows de CI/CD: "cuando aparezca un tag `v*`, dispara un deploy". Por eso importa que el nombre sea semánticamente significativo: [semver](https://semver.org/lang/es/) (`vMAJOR.MINOR.PATCH`) es lo más común.
Cosas que no hacer: mover o reescribir un tag ya publicado. Es posible con `-f`, pero rompe la confianza: otra persona que tenga el tag viejo verá que ahora apunta a otro commit. Si una release se rompió, haz una nueva (`v1.0.1`), no sobreescribas `v1.0.0`.
## `git fetch`: mirar sin tocar
`git fetch` trae datos del remoto a tu repo, pero **no los mezcla con tu trabajo**. Actualiza las referencias remotas (`origin/main`, `origin/feature-x`) pero tu working tree y tus ramas locales siguen como estaban.
```bash
git fetch # fetch de origin
git fetch origin # igual, explícito
git fetch --all # fetch de todos los remotos
git fetch --prune # fetch + borra referencias a ramas remotas ya borradas
git fetch origin main # solo la rama main de origin
```
¿Para qué sirve sin merge? Mucho más de lo que parece:
**Ver qué han subido los demás sin mezclarlo.** Tras `git fetch`, puedes mirar `git log origin/main` para ver los commits nuevos que aún no tienes, o `git diff main origin/main` para ver el diff. Si no te convence, no mergeas.
**Saber si tu rama está atrasada.** `git status` después de un fetch te dice "tu rama está N commits detrás de origin/main". Sin fetch no sabe.
**Preparar un rebase o un merge deliberado.** Yo prefiero `git fetch` + decisión manual a `git pull` ciego. Especialmente en ramas compartidas o en main.
**`--prune` es clave.** Con el tiempo acumulas referencias a ramas remotas (`origin/fix-algo`, `origin/feature-blabla`) que ya se borraron del servidor tras mergear PRs. `--prune` las limpia. Se puede hacer por defecto:
```bash
git config --global fetch.prune true
```
Y ya todos tus fetches limpian basura vieja automáticamente.
## Combinaciones del día a día
**Revisar el repo después de volver de vacaciones:**
```bash
git fetch --all --prune
git log --oneline --all --graph -20
```
Ves todo lo que ha pasado mientras no estabas, sin tocar nada.
**Hacer un hotfix sin perder lo que tenías a medias:**
```bash
git stash -m "Feature a medias"
git switch main
git pull --rebase
git switch -c hotfix/algo-urgente
# ... fix ...
git commit -am "Fix: validación de email"
git push -u origin hotfix/algo-urgente
git switch -
git stash pop
```
**Marcar una release y subirla:**
```bash
git switch main
git pull --rebase
git tag -a v2.0.0 -m "Release 2.0.0: migración a Go 1.24"
git push origin v2.0.0
```
## Errores típicos
**Olvidar `git stash pop` y seguir trabajando.** Si stasheas y te cambias de contexto, al volver verás el working tree limpio y pensarás que no tenías nada. `git stash list` de vez en cuando evita sorpresas.
**No pushear los tags.** "El tag no sale en el release"... porque no hiciste `git push origin v1.0.0`. Configurable con `git config --global push.followTags true` para que acompañen a los commits.
**Usar `git pull` en vez de `git fetch` cuando solo querías mirar.** `pull` implica merge (o rebase). Si no quieres integrar todavía, solo ver, usa fetch.
## Hasta aquí el intermedio
Con los nueve comandos de la serie intermedia (`branch`, `switch`, `merge`, `restore`, `reset`, `revert`, `stash`, `tag`, `fetch`) cubres todo lo que suele aparecer en un día de trabajo en equipo sin sobresaltos. Lo que queda son los comandos que usas menos pero que sacan las castañas del fuego cuando algo se tuerce: rebase para reescribir historia limpia, bisect para encontrar el commit que rompió algo, worktree para tener varias ramas checked out a la vez.
De todo eso va la **[serie avanzada](/search?tag=git-avanzado)**, que empieza en la próxima entrega con `git rebase`, `git cherry-pick` y `git commit --amend`. Si quieres repasar el nivel básico antes, sigue aquí: **[Git básico](/search?tag=git-basico)**.
---
# Git intermedio II: deshacer con cabeza
URL: https://javiervalencia.net/post/git-intermedio-ii-deshacer-con-cabeza
*Segunda entrega de la serie **[Git intermedio](/search?tag=git-intermedio)**. La anterior fue sobre **[ramas: branch, switch y merge](/search?tag=git-intermedio)**. Tiempo de lectura estimado: 5 minutos.*
Deshacer es donde la gente más se pone nerviosa con git, y con razón: algunos comandos de deshacer pueden borrarte trabajo de verdad. La buena noticia es que git tiene tres verbos distintos para deshacer, cada uno diseñado para un escenario concreto: `git restore`, `git reset` y `git revert`. Usarlos bien es cuestión de saber qué toca cada uno: ficheros, puntero de la rama, o historia publicada.
## `git restore`: deshacer cambios en ficheros
`git restore` es el "deshacer" moderno para ficheros. Llegó en git 2.23 junto con `switch`, para separar responsabilidades que antes mezclaba `checkout`. Se encarga exclusivamente de restaurar ficheros desde otro estado.
```bash
git restore fichero.go # descarta cambios en el working tree
git restore . # descarta todos los cambios no staged
git restore --staged fichero.go # saca un fichero del staging (unstage)
git restore --source=main f.go # trae el fichero como está en main
```
Casos que resuelve:
**"He tocado un fichero y me he arrepentido":**
```bash
git restore src/handler.go
```
El fichero vuelve a como estaba en el último commit. **Este comando es destructivo**: los cambios no staged se pierden para siempre, no están en ningún reflog. Si no estás seguro, primero `git diff` para ver qué vas a tirar, o `git stash` para guardarlos por si acaso.
**"He añadido algo con `git add` y quiero sacarlo del staging":**
```bash
git restore --staged src/handler.go
```
El fichero sale del staging area pero mantiene los cambios en el working tree. Es lo opuesto de `git add`. Git te sugiere este comando literalmente en el output de `git status` cuando tienes cosas stageadas. Léete lo que te dice git.
**"Quiero traer un fichero tal como está en otra rama":**
```bash
git restore --source=main README.md
```
Copia README.md desde la rama `main` al working tree actual. No cambia de rama, solo trae el fichero. Útil para "recuperar" un fichero que borraste sin querer en tu rama pero sigue bien en main.
El gran mensaje: `restore` solo toca ficheros del working tree o del staging. Nunca toca commits ni mueve ramas. Es tan seguro como destructivo puede ser un `rm`.
## `git reset`: mover el puntero de la rama
`git reset` mueve el puntero de la rama actual a otro commit. Eso puede sonar abstracto, pero el efecto práctico es "deshacer commits". Hay tres modos:
```bash
git reset --soft HEAD~1 # deshace el commit, conserva cambios en staging
git reset --mixed HEAD~1 # deshace el commit, saca cambios del staging
git reset --hard HEAD~1 # deshace el commit y TIRA los cambios
```
`HEAD~1` significa "el commit anterior al actual". Puedes usar cualquier referencia: `HEAD~3`, `abc123`, nombre de rama, etc.
**`--soft`** es el más conservador: mueve la rama hacia atrás pero los cambios de los commits "borrados" quedan en staging, listos para recommitear. Útil cuando quieres fusionar dos commits en uno, o cuando te diste cuenta de que el mensaje está mal.
**`--mixed`** (el valor por defecto si no especificas modo) mueve la rama y deja los cambios en el working tree sin staged. Lo uso cuando quiero deshacer un commit y reorganizar qué entra en el siguiente.
**`--hard`** es el peligroso: mueve la rama y borra todo lo que no sea exactamente ese commit. Working tree y staging, al agua. Si ejecutas `--hard` con cambios sin guardar, se van. Sin reflog, sin nada.
El truco de seguridad: casi cualquier cosa que hayas perdido con `reset --hard` sigue en `git reflog` durante un tiempo. Puedes volver con `git reset --hard ORIG_HEAD` o `git reset --hard HEAD@{1}`. Pero mejor no probar, mejor no meter `--hard` sin estar seguro.
**Norma esencial**: `reset` reescribe historia local. Si los commits que vas a deshacer ya los has pusheado a una rama compartida, usar reset significa dejar la rama desincronizada y, si haces force push, borrar trabajo en el remoto. Para deshacer algo ya pusheado y compartido, usa `revert`, no `reset`.
## `git revert`: deshacer sin reescribir historia
`git revert` hace lo contrario: en vez de borrar un commit, crea un nuevo commit que deshace los cambios del anterior. La historia queda intacta: el commit original sigue ahí, con un "hermano gemelo" al final que lo neutraliza.
```bash
git revert abc123 # crea un commit que revierte abc123
git revert HEAD # revierte el último commit
git revert --no-commit HEAD # prepara la reversión pero no la commitea
git revert abc123..def456 # revierte un rango de commits
```
Cuándo usar revert:
- El commit malo ya está en una rama compartida o en producción.
- No puedes/no quieres reescribir la historia.
- Quieres que quede constancia de "esto se revirtió, a propósito, tal día".
Durante revert puede haber conflictos si los cambios a revertir chocan con trabajo posterior. Se resuelven igual que en un merge: editas los ficheros con marcadores `<<<<<<<`, haces `git add`, `git revert --continue`. O `git revert --abort` si te arrepientes.
El mensaje del commit por defecto es del tipo `Revert "Mensaje original del commit"`. Edítalo para explicar **por qué** reviertes. "Revert X porque rompió el pipeline de checkout en producción" le dice más a alguien que lea la historia dentro de seis meses.
## Tabla mental: qué usar cuándo
| Situación | Comando |
|-----------|---------|
| Tocaste un fichero y quieres tirarlo | `git restore fichero` |
| Hiciste `git add` por error | `git restore --staged fichero` |
| El último commit está mal, quieres rehacerlo | `git reset --soft HEAD~1` |
| Quieres deshacer los dos últimos commits y sus cambios, trabajo local | `git reset --hard HEAD~2` |
| Un commit viejo (ya compartido) está mal y hay que anularlo | `git revert abc123` |
Mi regla de oro: si el commit está solo en tu rama y no lo has pusheado, puedes usar `reset` con tranquilidad. Si ya lo ha visto alguien más, usa `revert`.
## El comando que te salva
Uno que no es "deshacer" pero es la red de seguridad detrás de todos los anteriores:
```bash
git reflog
```
`reflog` te muestra el historial de dónde ha estado `HEAD`: cada commit, cada cambio de rama, cada reset. Incluso commits "borrados" por `reset --hard` siguen ahí durante unos 30 días (por defecto).
Si has hecho un reset destructivo y quieres volver atrás:
```bash
git reflog # busca el hash donde estabas antes
git reset --hard HEAD@{3} # vuelves a ese estado
```
Lo veremos en detalle en la [serie avanzada](/search?tag=git-avanzado), pero vale la pena saber que existe desde ahora: git rara vez pierde datos de verdad, incluso cuando parece que sí.
## Errores típicos al deshacer
**Usar `reset --hard` sin leer qué estás deshaciendo.** Dedica dos segundos a `git status` y `git log --oneline -5` antes.
**Revertir un commit viejo sin entender las dependencias.** Si has reverted algo y hay commits posteriores que construían sobre eso, puede hacer falta revertir más o resolver conflictos. No es automático.
**Confundir `revert` con "volver atrás" en el sentido de checkout.** `git revert abc123` no lleva tu rama a abc123: crea un commit nuevo. Para "volver" a un estado antiguo, lo que necesitas es `reset` o `checkout` de un commit específico.
## Lo que viene
Con restore, reset y revert cubres todas las formas cuerdas de deshacer en git. En la **[siguiente entrega](/search?tag=git-intermedio)** toca el kit de bolsillo intermedio: `git stash` para guardar trabajo a medias, `git tag` para marcar versiones y `git fetch` para mirar qué hay en el remoto sin mezclarlo con tu trabajo. Y después viene el salto a la **[serie avanzada](/search?tag=git-avanzado)**, donde usaremos reflog a fondo.
---
# PostgreSQL desde cero (I): instalación, psql y primeros pasos
URL: https://javiervalencia.net/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos
*Primera entrega de la serie **[PostgreSQL desde cero a pro](/search?tag=postgresql-desde-cero)**. Tiempo de lectura estimado: 10 minutos.*
Arranco una nueva serie de cinco posts, esta vez sobre PostgreSQL. Es la base de datos que más uso y la que más respeto. La idea es ir de no haber tocado nunca PostgreSQL a ser capaz de montarlo en producción con replicación, backups y monitorización decentes.
Si quieres una visión transversal del ecosistema de bases de datos para analítica y por qué a veces no basta con PostgreSQL, la [serie ClickHouse desde cero a pro](/search?tag=clickhouse-desde-cero) es un buen complemento.
## Por qué PostgreSQL
En un mundo lleno de bases de datos exóticas, PostgreSQL sigue siendo la opción por defecto para la mayoría de aplicaciones. Motivos:
- **ACID serio**: transacciones, MVCC, aislamiento configurable.
- **Extensible**: tipos propios, funciones, extensiones como PostGIS, TimescaleDB, pgvector.
- **SQL moderno**: CTEs, window functions, LATERAL, JSON/JSONB, arrays nativos.
- **Fiabilidad probada**: más de 25 años de desarrollo, usado en producción desde startups a bancos.
- **Licencia permisiva** (PostgreSQL License, similar a BSD/MIT).
Si tu aplicación no tiene requisitos muy específicos, la respuesta correcta a "¿qué base de datos uso?" es casi siempre PostgreSQL.
## Instalación
Hay tres caminos razonables para empezar:
1. **Paquetes oficiales** sobre Debian/Ubuntu/RHEL. Lo que usarás en producción.
2. **Docker**. Lo mejor para probar en local sin ensuciar el sistema.
3. **Servicios gestionados**: RDS, Cloud SQL, Neon, Supabase, Crunchy Bridge...
Para esta serie usamos paquetes nativos y Docker indistintamente. Lo que aprendas vale igual para un servicio gestionado (con matices de configuración que veremos en la [entrega V](/post/postgresql-desde-cero-v-replicacion-backups-y-produccion)).
### Instalación en Debian/Ubuntu
```bash
sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
sudo apt install -y postgresql-17 postgresql-client-17
sudo systemctl enable --now postgresql
```
El script oficial añade el repositorio de la PostgreSQL Global Development Group, que mantiene versiones recientes para distribuciones LTS. Los paquetes del repo base suelen ir una o dos versiones por detrás.
### Instalación con Docker
```bash
docker run -d \
--name pg \
-e POSTGRES_PASSWORD=secret \
-p 5432:5432 \
-v pgdata:/var/lib/postgresql/data \
postgres:17
```
El volumen `pgdata` persiste los datos entre reinicios. Puerto 5432 es el estándar.
Para entrar al cliente:
```bash
docker exec -it pg psql -U postgres
```
## psql: el cliente que vas a usar cada día
`psql` es el cliente de consola oficial. Es mucho más que un prompt SQL: tiene meta-comandos, ejecución de scripts, formatos de salida, variables, historial persistente y autocompletado.
Meta-comandos que uso constantemente:
```
\l -- listar bases de datos
\c nombre -- conectarse a una base de datos
\dt -- listar tablas
\dt+ -- con tamaños y descripciones
\d tabla -- describir una tabla
\d+ tabla -- con detalles extra
\di -- listar índices
\df -- listar funciones
\du -- listar roles
\dn -- listar schemas
\dx -- listar extensiones instaladas
\timing on -- mostrar tiempo de ejecución
\x -- toggle formato expandido (útil para filas anchas)
\e -- abrir $EDITOR para escribir la consulta
\i fichero.sql -- ejecutar script
\q -- salir
\? -- ayuda de meta-comandos
```
Mi `~/.psqlrc` básico:
```psql
\set QUIET 1
\pset null '(null)'
\pset border 2
\set PROMPT1 '%n@%/%R%# '
\set HISTFILE ~/.psql_history- :DBNAME
\set HISTCONTROL ignoredups
\set COMP_KEYWORD_CASE upper
\timing on
\set QUIET 0
```
Con esto ves el nombre de la base de datos en el prompt, los nulos no se confunden con strings vacíos, el historial se separa por base de datos, y tienes el tiempo de cada consulta automáticamente.
## Roles, bases de datos y permisos
En PostgreSQL todo es un **rol**. Un rol puede ser "usuario" (`LOGIN`) o "grupo" (`NOLOGIN`). Los permisos se otorgan a roles. Un rol puede heredar permisos de otro.
```sql
-- Crear un rol de aplicación sin permisos de login directo
CREATE ROLE app NOLOGIN;
-- Crear un usuario real
CREATE ROLE javier WITH LOGIN PASSWORD 'xxx' IN ROLE app;
-- Crear una base de datos cuyo owner es el rol app
CREATE DATABASE blog OWNER app;
-- Dar permisos
GRANT CONNECT ON DATABASE blog TO app;
GRANT USAGE ON SCHEMA public TO app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app;
-- Y lo más importante: que se apliquen a tablas futuras
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app;
```
Separar el rol dueño (propietario de las tablas) del rol de aplicación (el que usa la app en runtime) es una buena práctica: limita el daño si las credenciales se filtran.
### pg_hba.conf
El fichero `pg_hba.conf` controla quién puede conectarse, desde dónde y con qué método de autenticación. Está en `/etc/postgresql/17/main/` en Debian/Ubuntu.
Entrada típica para entornos de producción:
```
# TYPE DATABASE USER ADDRESS METHOD
local all all peer
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
hostssl all all 10.0.0.0/8 scram-sha-256
```
- `peer`: autenticación por usuario del sistema operativo (solo en socket local).
- `scram-sha-256`: el método moderno de autenticación por contraseña.
- `hostssl`: solo acepta conexiones cifradas.
## Tu primera base de datos
Vamos a montar un esquema de blog muy simple. Desde `psql`:
```sql
\c blog
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
author_id BIGINT NOT NULL REFERENCES authors(id) ON DELETE CASCADE,
title TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
body TEXT NOT NULL,
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX posts_published_at_idx ON posts(published_at DESC) WHERE published_at IS NOT NULL;
```
Un par de detalles que ya introducen ideas clave:
- `BIGSERIAL` genera una columna `BIGINT` con una secuencia asociada. En versiones modernas hay una alternativa más estándar, `GENERATED ALWAYS AS IDENTITY`, que veremos en la [entrega II](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones).
- `TIMESTAMPTZ` (timestamp with time zone) es casi siempre lo que quieres. Guarda el instante en UTC y lo convierte al huso horario de la sesión al leer. El `TIMESTAMP` sin zona suele ser una trampa.
- `REFERENCES ... ON DELETE CASCADE` crea una foreign key: borrar un autor borra sus posts.
- El último índice es **parcial**: solo indexa filas publicadas. Si la mayoría de posts son borradores, el índice es pequeño y más rápido.
## Primeros INSERT y SELECT
```sql
INSERT INTO authors (name, email)
VALUES ('Javier', 'javier@example.com')
RETURNING id;
-- Devuelve 1
INSERT INTO posts (author_id, title, slug, body, published_at)
VALUES
(1, 'Hola mundo', 'hola-mundo', 'Primer post', now()),
(1, 'En borrador', 'borrador', 'Aún no publicado', NULL);
SELECT id, title, published_at IS NOT NULL AS publicado
FROM posts
ORDER BY created_at DESC;
```
`RETURNING` es una de esas pequeñas cosas que hacen que PostgreSQL sea tan agradable: cualquier `INSERT`, `UPDATE` o `DELETE` puede devolver filas. Adiós al `SELECT LAST_INSERT_ID()`.
## Transacciones
Toda operación en PostgreSQL ocurre dentro de una transacción (implícita o explícita). Para operaciones multi-statement:
```sql
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- Si algo sale mal
-- ROLLBACK;
COMMIT;
```
Los savepoints permiten hacer rollback parcial dentro de una transacción grande:
```sql
BEGIN;
INSERT INTO ...;
SAVEPOINT sp1;
UPDATE ...; -- esto puede fallar
ROLLBACK TO SAVEPOINT sp1;
COMMIT; -- el INSERT inicial sí se guarda
```
Por defecto el nivel de aislamiento es `READ COMMITTED`. Para operaciones financieras o cargas analíticas complejas, `REPEATABLE READ` o `SERIALIZABLE` son alternativas importantes que conviene conocer.
## Los ficheros que importan
Tres ficheros concentran casi toda la configuración:
- **`postgresql.conf`** — parámetros del servidor: memoria, WAL, logs, conexiones.
- **`pg_hba.conf`** — autenticación y redes permitidas.
- **`pg_ident.conf`** — mapeo entre usuarios del SO y roles de PostgreSQL.
En Debian/Ubuntu están en `/etc/postgresql/17/main/`. El directorio de datos (`PGDATA`) está en `/var/lib/postgresql/17/main/`. Ahí viven los WAL, las tablas, los índices.
Para consultar cualquier parámetro desde `psql`:
```sql
SHOW work_mem;
SHOW shared_buffers;
SHOW max_connections;
```
Y para ver todos:
```sql
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name LIKE '%mem%';
```
## Por dónde seguir
- **[II: tipos, restricciones y relaciones](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones)** — el sistema de tipos, claves, CHECK, JSON/JSONB, arrays.
- **[III: CTEs, window functions y consultas avanzadas](/post/postgresql-desde-cero-iii-ctes-window-functions-y-consultas-avanzadas)** — lo que diferencia a un desarrollador de PostgreSQL de uno que "sabe SQL".
- **[IV: índices, EXPLAIN y rendimiento](/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento)** — cuando la consulta tarda tres segundos y tu jefe ya no sonríe.
- **[V: replicación, backups y producción](/post/postgresql-desde-cero-v-replicacion-backups-y-produccion)** — operar PostgreSQL cuando el negocio depende de ello.
Si te gustan las consultas modernas y quieres un aperitivo, el post [PostgreSQL: 10 consultas que todo desarrollador debería conocer](/post/postgresql-10-consultas-que-todo-desarrollador-deberia-conocer) es un buen complemento a esta serie.
---
# Git intermedio I: trabajar con ramas
URL: https://javiervalencia.net/post/git-intermedio-i-trabajar-con-ramas
*Primera entrega de la serie **[Git intermedio](/search?tag=git-intermedio)**. Si vienes del nivel básico (**[init, clone y status](/search?tag=git-basico)**, el ciclo de cambios, push y pull), ahora toca lo que de verdad convierte a git en una herramienta potente. Tiempo de lectura estimado: 5 minutos.*
Las ramas son la razón por la que git se comió al resto de sistemas de control de versiones. En git son baratísimas, casi gratis: una rama es un puntero ligero a un commit, no una copia del código. Crear, cambiar y fusionar ramas es el 80% de lo que separa a alguien que "usa" git de alguien que "sufre" git. Tres comandos para esta entrega: `git branch`, `git switch` y `git merge`.
## `git branch`: listar, crear y borrar ramas
`git branch` sin argumentos lista las ramas locales, marcando con un asterisco la actual:
```bash
$ git branch
* main
feature-login
fix-validacion
```
Con flags, hace más cosas:
```bash
git branch -r # solo ramas remotas
git branch -a # locales + remotas
git branch mi-rama # crea una rama nueva (no se cambia a ella)
git branch -d mi-rama # borra una rama ya fusionada
git branch -D mi-rama # borra sin comprobar si está fusionada
git branch -m nombre-viejo nuevo # renombra una rama
```
La diferencia entre `-d` y `-D` es importante: con `-d`, git te protege si la rama tiene commits que no están en ningún otro sitio; con `-D`, lo borras a pelo. Usa `-d` por defecto; si git se niega, piensa antes de pasar a `-D`.
El flag que más uso: `--merged` y `--no-merged`:
```bash
git branch --merged main # ramas ya integradas en main (se pueden borrar)
git branch --no-merged main # ramas con trabajo sin integrar
```
Combinado con `xargs` limpias ramas muertas en un comando. Lo vimos en el post de git aliases: `git branch --merged main | grep -v '^\*\|main' | xargs git branch -d`.
Para ver de qué commit parte una rama y cuál es su estado respecto al remoto:
```bash
git branch -vv
```
La doble v es "verbose + tracking": muestra el hash del último commit, el mensaje y qué rama remota trackea cada local.
## `git switch`: cambiar de rama (sin pisar nada)
`git switch` llegó en git 2.23 como alternativa moderna a `git checkout` para la parte de cambiar de rama. `checkout` sigue funcionando, pero hace demasiadas cosas (cambiar rama, recuperar ficheros, reconstruir working tree) y es fácil confundirse. `switch` solo cambia de rama: más seguro, más claro.
```bash
git switch mi-rama # cambia a mi-rama
git switch - # vuelve a la rama anterior (como cd -)
git switch -c nueva-rama # crea y cambia a nueva-rama
git switch -c fix main # crea fix desde main y cambia
git switch --detach abc123 # checkout de un commit concreto
```
El `-c` es "create" y es el que uso más. Cuando empiezo a trabajar en algo nuevo:
```bash
git switch -c feature/login-oauth
```
Una rama nueva desde donde estaba, con el nombre dicho. Si la rama ya existía, error (no te la sobrescribe). Si necesitas partir desde otra base:
```bash
git switch -c feature/login-oauth main
```
Cosas que `switch` hace bien y `checkout` te dejaba meter la pata:
- Si tienes cambios locales sin committear que entrarían en conflicto con el cambio, `switch` aborta limpiamente. Con `checkout` a veces aplicaba medio cambio.
- No permite restaurar ficheros individuales (para eso está `git restore`, que veremos en el siguiente post).
El truco que más agradezco: `git switch -` (guion). Te lleva a la rama anterior, como `cd -`. Súper útil cuando vas alternando entre tu rama y main.
## `git merge`: fusionar una rama en otra
`git merge` trae los cambios de una rama a la actual. El patrón típico: estás en `main`, quieres integrar lo de `feature/login`:
```bash
git switch main
git merge feature/login
```
Git intentará una de dos cosas:
1. **Fast-forward**: si `main` no ha avanzado desde que saliste a crear la rama, git simplemente mueve el puntero de `main` al último commit de `feature/login`. Historial limpio, sin merge commit.
2. **Three-way merge**: si ambas ramas tienen commits nuevos, git calcula el ancestro común y combina los dos caminos, creando un "merge commit" que tiene dos padres.
Para forzar siempre merge commit (útil en workflows tipo GitHub Flow):
```bash
git merge --no-ff feature/login
```
Para forzar fast-forward o fallar (útil si no quieres merge commits nunca):
```bash
git merge --ff-only feature/login
```
Lo que pasa durante un merge:
- Si los cambios no tocan las mismas líneas, git los fusiona solo y crea el commit.
- Si hay conflictos, git marca los ficheros, te deja los conflictos escritos con `<<<<<<<`, `=======`, `>>>>>>>` y espera a que edites. Tras editar: `git add` los ficheros, `git commit` para completar.
Durante un merge con conflictos puedes abortarlo y quedarte como estabas:
```bash
git merge --abort
```
Este es uno de los comandos que más tranquilidad da: si te has metido en un merge feo y no sabes cómo salir, `--abort` te devuelve al estado justo antes.
Alternativa a merge: `git rebase`. Pero eso es material de la [serie avanzada](/search?tag=git-avanzado). Por ahora, merge es suficiente.
## El flujo de ramas en la práctica
Un ciclo normal de feature:
```bash
git switch main
git pull --rebase # arranco con main al día
git switch -c feature/exportar-csv
# ... trabajo, commits ...
git push -u origin feature/exportar-csv
# ... más trabajo, más commits, más push ...
# cuando termino: abro PR, se revisa, se mergea en main
git switch main
git pull --rebase
git branch -d feature/exportar-csv # limpio la rama local
```
Ramas cortas, enfocadas, con un propósito claro. Eso es la mayor parte del trabajo.
## Nombres de ramas: convención
Los nombres importan más de lo que parece porque `git branch -vv` los lista junto a cincuenta otros. Convenciones razonables:
- Prefijo por tipo: `feature/`, `fix/`, `chore/`, `docs/`.
- En inglés o español, pero consistente en todo el repo.
- Cortos pero descriptivos: `fix/login-timeout` mejor que `fix/bug` o que `fix/login-timeout-when-redis-is-down-on-tuesdays`.
- Sin espacios ni mayúsculas. Git lo permite pero te va a molestar al autocompletar.
Algunos equipos meten número de ticket: `feature/PROJ-123-exportar-csv`. Útil si tu gestor de tareas no enlaza automáticamente las ramas; redundante si sí.
## Errores típicos con ramas
**Trabajar directamente en main.** Cuesta mucho deshacer commits mezclados en main. Siempre rama por feature, incluso para cambios pequeños.
**Ramas enormes que viven un mes.** Cuanto más vive una rama, más difícil es mergear sin conflictos. Divide en ramas más pequeñas; mergea pronto y seguido.
**Olvidarse de traer main antes de crear rama.** Si creas `feature/X` desde un main viejo, al mergear chocarás con todo lo que el equipo ha metido desde entonces. Siempre `git switch main && git pull` antes de `git switch -c`.
## Lo que viene
Con branch, switch y merge tienes el trabajo "normal" cubierto. En la **[siguiente entrega](/search?tag=git-intermedio)** llega lo que hacemos cuando las cosas se tuercen: `git restore`, `git reset` y `git revert`. Tres formas muy distintas de deshacer, con niveles de peligro muy distintos también.
---
# Git básico III: sincronizar con el remoto
URL: https://javiervalencia.net/post/git-basico-iii-sincronizar-con-el-remoto
*Tercera entrega de la serie **[Git básico](/search?tag=git-basico)**. Las anteriores: **[crear y clonar repos](/search?tag=git-basico)** y **[el ciclo de cambios](/search?tag=git-basico)**. Tiempo de lectura estimado: 5 minutos.*
Hasta aquí todo lo que hemos hecho vive en tu máquina. Los commits están en tu repo local y nadie más los ve. Para que el trabajo sea útil al resto del equipo (o a ti mismo desde otra máquina) hace falta sincronizar con un servidor remoto. Los tres comandos de hoy: `git push`, `git pull` y `git log`. Los dos primeros mueven commits entre tu máquina y el remoto; el tercero te deja leer qué ha pasado.
## `git push`: subir tus commits al remoto
`git push` envía los commits que tienes en local (y que aún no están en el remoto) hacia el servidor. La forma más común:
```bash
git push
```
Eso funciona si tu rama local ya tiene configurado qué rama remota trackea. Cuando empujas una rama por primera vez, git no sabe dónde va y hay que decírselo:
```bash
git push -u origin mi-rama
```
`-u` significa "upstream": establece el tracking, de modo que a partir de ahí puedes usar `git push` a secas. `origin` es el nombre por defecto del remoto (el que se configuró al hacer clone). `mi-rama` es la rama que quieres enviar.
Errores típicos:
- **"Updates were rejected"**: alguien subió commits a la misma rama después de que tú hicieras tu último pull. Solución: `git pull`, resolver conflictos si los hay, volver a intentar.
- **"failed to push some refs"**: misma causa, normalmente. No intentes "arreglarlo" con `--force` sin saber lo que haces. El force push sobre una rama compartida borra commits de otros.
Sobre `--force`: **existe** pero no es para repos compartidos. Si has hecho rebase o amend de commits que ya habías empujado a tu rama, `--force-with-lease` es más seguro que `--force`: aborta si ha habido cambios nuevos en el remoto que no tienes en local. Es el mínimo civilizado cuando necesitas sobrescribir.
```bash
git push --force-with-lease # "force, pero solo si nadie más ha tocado"
```
## `git pull`: bajar lo que han subido otros
`git pull` trae al repo local los commits que están en el remoto y no tienes tú. Funcionalmente son dos operaciones encadenadas: `git fetch` (descarga los commits al repo, pero no los aplica) y `git merge` (los integra en tu rama actual).
```bash
git pull # fetch + merge en la rama actual
git pull --rebase # fetch + rebase en vez de merge
```
La diferencia entre `merge` y `rebase` importa: con merge, si había commits divergentes, git crea un "merge commit" que une las dos líneas. Con rebase, tus commits se reaplican encima de lo que haya en el remoto, quedando un historial lineal.
Yo prefiero `pull --rebase` casi siempre, porque odio los merge commits vacíos de "Merge branch 'main' of origin/main" que aparecen cuando dos personas hacen pull al mismo tiempo. Se puede hacer que sea el comportamiento por defecto:
```bash
git config --global pull.rebase true
```
Cuándo `pull --rebase` te va a doler: si tienes commits propios sin pushear y los de otros tocan lo mismo, te toca resolver conflictos durante el rebase, commit a commit. Es manejable, pero si no te sientes cómodo con rebase, usa pull a secas.
Error clásico: hacer `git pull` con cambios sin committear que están tocando ficheros que el pull va a modificar. Git se niega y te pide que hagas stash, commit o descartes. Normalmente lo resuelves con `git stash`, `git pull`, `git stash pop` (lo veremos en el post sobre stash).
## `git log`: leer la historia
`git log` te muestra el historial de commits: quién, cuándo, qué. Sin argumentos, es verboso y poco útil. Con los flags adecuados se convierte en el comando que más vas a usar para investigar.
```bash
git log --oneline # una línea por commit
git log --oneline --graph # con el grafo de ramas al margen
git log --oneline --graph --all # todas las ramas, no solo la actual
```
La combinación `--oneline --graph --all --decorate` (que suele meterse en un alias) te da un árbol compacto con hash corto, mensaje y referencias (ramas y tags). Es la vista por defecto que deberías mirar cuando un repo te parece "que está raro".
Filtros que se usan mucho:
```bash
git log --author="Alicia" # commits de una persona
git log --since="2 weeks ago" # últimos 14 días
git log --grep="bug" # commits cuyo mensaje contiene "bug"
git log -- src/handler.go # commits que tocan un fichero
git log -p -- src/handler.go # igual pero con el diff de cada commit
```
El `--` antes del nombre de fichero es importante: le dice a git "lo que viene ahora son rutas, no ramas ni refs". En ficheros con nombres normales se puede omitir, pero si tu rama se llama igual que un fichero, hay que ponerlo.
Mi combinación favorita para buscar cuándo se introdujo una línea concreta:
```bash
git log -S "funcionSospechosa" --oneline -- src/
```
`-S` (pickaxe) busca commits donde el texto aparece o desaparece en el diff. Perfecto para "¿cuándo se añadió esta llamada?" o "¿cuándo la borraron?".
Para un log tipo "página de gitk en terminal":
```bash
git log --graph --pretty=format:'%C(yellow)%h%Creset -%C(red)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
```
Es largo, pero cabe en un alias y luego se usa con dos caracteres. El [post de git aliases](/search?q=git+aliases) ya publicado cubre este en detalle.
## El ciclo con remoto en vivo
Un día típico:
```bash
git pull --rebase # traigo lo nuevo del equipo
# ... edito código ...
git status # veo qué he tocado
git add -p # añado selectivamente
git commit -m "Arreglar validación de email"
git log --oneline -5 # reviso mis últimos commits
git push # subo mi trabajo
```
Con estos tres comandos (más los seis anteriores de la serie) tienes cubierto el 80% del trabajo diario con git en solitario o en equipos pequeños.
## Errores típicos con el remoto
**Hacer force push sin `--force-with-lease`**. Si trabajas con más gente, una línea sin `--force-with-lease` puede borrar commits ajenos silenciosamente. Nunca fuerces un push sin saber si alguien ha tocado esa rama.
**Confundir `origin` con `upstream`**. En proyectos forkeados la convención es: `origin` apunta a tu fork, `upstream` al repo original. `git push` va a origin; para sincronizar con upstream se hace `git fetch upstream`.
**Pullear con trabajo sin guardar**. Si no estás seguro, primero `git status`. Si hay cosas sin committear, stash o commit antes de pullear. El mensaje de git en ese caso es claro: léelo.
## Hasta aquí el nivel básico
Con los nueve comandos de esta serie de tres posts (`init`, `clone`, `status`, `add`, `commit`, `diff`, `pull`, `push`, `log`) puedes trabajar en git de forma competente durante el 80% de los días. El 20% restante es donde las cosas se ponen interesantes: ramas, deshacer, resolver follones. De eso trata la **[serie intermedia](/search?tag=git-intermedio)**, que empieza en la próxima entrega con `git branch`, `git switch` y `git merge`. Y cuando quieras profundizar más, te espera la **[serie avanzada](/search?tag=git-avanzado)**.
---
# Git básico II: el ciclo de cambios
URL: https://javiervalencia.net/post/git-basico-ii-el-ciclo-de-cambios
*Segunda entrega de la serie **[Git básico](/search?tag=git-basico)**. Si te perdiste la primera, empieza por **[crear y clonar repositorios](/search?tag=git-basico)**. Tiempo de lectura estimado: 5 minutos.*
En el post anterior de la serie vimos cómo crear o clonar un repositorio y consultar su estado. Hoy toca lo que vas a hacer cientos de veces al día: preparar un conjunto de cambios, guardarlos como un commit y revisar qué has modificado antes de hacerlo. Tres comandos: `git add`, `git commit` y `git diff`.
## El modelo de tres zonas
Antes de los comandos, una imagen mental. Git tiene tres "zonas" donde viven tus cambios:
- **Working tree**: los ficheros tal cual los ves y editas.
- **Staging area (o index)**: una zona intermedia donde preparas lo que va a ir en el próximo commit.
- **Repositorio**: el histórico, los commits ya guardados.
El flujo es: editas en el working tree, promueves cambios al staging area con `git add`, y los congelas como commit en el repo con `git commit`. El staging area es lo que distingue a git de sistemas más simples: te permite hacer commits quirúrgicos, solo de una parte de lo que has tocado.
## `git add`: preparar cambios para el commit
`git add` pasa cambios del working tree al staging area. No guarda nada en el histórico todavía; solo dice "esto es lo que quiero que entre en el próximo commit".
```bash
git add README.md # añade un fichero concreto
git add src/ # añade todo lo modificado en una carpeta
git add . # añade todo lo cambiado en el directorio actual
git add -A # añade todos los cambios del repo entero
```
La diferencia entre `.` y `-A` es sutil pero importante: `.` solo afecta al directorio actual y subcarpetas; `-A` considera todo el repo aunque estés metido en una subcarpeta. En la práctica, si estás en la raíz del repo dan casi lo mismo.
La opción que de verdad merece la pena aprender es `-p`:
```bash
git add -p
```
`-p` es interactivo: git te muestra los cambios por "hunks" (bloques) y te pregunta uno a uno si quieres añadirlos. Escribes `y` (sí), `n` (no), `s` (partir el hunk en dos) o `e` (editar manualmente). Esto te permite hacer commits separados de cambios que están mezclados en el mismo fichero. Lo uso constantemente cuando llevo un rato programando y veo que he tocado dos cosas distintas que deberían ser dos commits.
Un error típico: usar `git add .` sin mirar qué estás añadiendo. Un día incluyes sin querer un fichero con credenciales, un binario enorme o notas personales. La solución es el par `git status` seguido de `git add` de ficheros concretos, no el `git add .` a ciegas.
Para "despreparar" algo que añadiste por error:
```bash
git restore --staged README.md
```
Git mismo te sugiere este comando en el output de `git status` cuando tienes cosas en staging. No hace falta memorizarlo.
## `git commit`: guardar los cambios preparados
`git commit` toma lo que hay en el staging area y crea un commit: un snapshot con un mensaje, un autor y una fecha. Una vez hecho, ese commit queda en el histórico del repo local. No en el remoto: para eso hace falta hacer push, que veremos en el siguiente post.
```bash
git commit # abre el editor para el mensaje
git commit -m "Añadir README inicial" # mensaje en una línea
git commit -am "Fix typo" # add automático + commit
```
El `-a` mete en staging todos los ficheros ya trackeados que hayan cambiado, y luego hace commit. Pero ojo: **no** añade ficheros nuevos (untracked). Para esos, siempre `git add` explícito.
Sobre el mensaje: gastar treinta segundos en un buen mensaje ahorra horas en el futuro. Convención razonable: primera línea corta (50 caracteres máximo) en imperativo ("Arreglar validación de email" en vez de "Arreglé la validación"), luego una línea en blanco, luego un párrafo explicando el por qué si hace falta. No repitas en el mensaje lo que el diff ya dice; explica lo que el diff no cuenta.
Lo que no se hace:
- Commits con mensaje "wip", "cambios" o "fix" y punto. En tres meses no tendrás ni idea de qué hicieron.
- Commits gigantes que tocan cinco cosas distintas. Divide; usa `git add -p`.
- Amend o rebase sobre commits que ya has empujado a una rama compartida. En tu rama local haz lo que quieras; en la rama de todos, no reescribas historia ya publicada.
## `git diff`: ver exactamente qué has cambiado
`git diff` te muestra las diferencias entre dos estados. Sin argumentos, compara el working tree con el staging area: o sea, lo que has tocado pero no has añadido aún.
```bash
git diff # working tree vs staging
git diff --staged # staging vs último commit
git diff HEAD # working tree vs último commit (incluye staged)
git diff main # working tree vs otra rama
git diff main..develop # diferencias entre dos ramas
```
El que más uso es `--staged` (también escrito `--cached`): justo antes de hacer `git commit`, ejecuto `git diff --staged` para ver exactamente lo que voy a committear. Es mi última línea de defensa contra meter código comentado, `console.log` sueltos o trozos de debug. Dos segundos de revisión y evitas un commit avergonzante.
Flags útiles:
```bash
git diff --stat # resumen: ficheros y líneas añadidas/quitadas
git diff --word-diff # diff por palabras, no por líneas
git diff -- ruta # limita a un fichero o carpeta
```
`--stat` es el resumen rápido: si el diff completo tiene mil líneas, `--stat` te da un mapa de ocho líneas con los ficheros y su tamaño de cambio. Es la versión "índice" del diff.
## El ciclo en vivo
Un ejemplo de sesión realista:
```bash
git status # veo qué tengo cambiado
git diff # miro los cambios sin staged
git add src/handler.go src/utils.go # añado dos ficheros concretos
git diff --staged # reviso lo que voy a commitear
git commit -m "Validar email antes de crear usuario"
git status # confirmo que todo está limpio
```
Tres minutos. Cinco comandos. Esto, multiplicado por diez o veinte veces al día, es buena parte del trabajo con git.
## Errores típicos de esta fase
**Commitear secretos.** Una vez un token está en la historia de git, quitarlo es doloroso (y si ya hiciste push, prácticamente irreversible). Usa `.gitignore` y revisa siempre con `git diff --staged` antes de commitear.
**Ignorar lo que dice `git status`.** Git te dice en texto plano qué hacer. Si te pone "use git restore", haz caso: funciona.
**Hacer commits enormes "para no dejar cabos sueltos".** Al revés: muchos commits pequeños son más fáciles de revisar, revertir y entender. Si estás tocando varias cosas, haz varios commits con `git add -p`.
## Lo que viene
Con add, commit y diff ya tienes el ciclo local completo. Pero los commits locales no los ve nadie más. En la **[tercera entrega de la serie](/search?tag=git-basico)**, la conexión con el mundo: `git pull`, `git push` y `git log`. Sincronizar tu trabajo con un remoto y consultar la historia de quién hizo qué.
Más adelante, cuando quieras dar el salto, están las series **[intermedia](/search?tag=git-intermedio)** y **[avanzada](/search?tag=git-avanzado)**.
---
# Git básico I: crear y clonar repositorios
URL: https://javiervalencia.net/post/git-basico-i-crear-y-clonar-repositorios
*Primera entrega de la serie **[Git básico](/search?tag=git-basico)**. Forma parte de un recorrido más amplio por git en tres niveles: **[básico](/search?tag=git-basico)**, **[intermedio](/search?tag=git-intermedio)** y **[avanzado](/search?tag=git-avanzado)**. Tiempo de lectura estimado: 5 minutos.*
Arrancamos una serie de nueve posts sobre git organizados en tres niveles: básico, intermedio y avanzado. Tres comandos por entrega, tres entregas por nivel. La idea es cubrir lo que de verdad se usa cada día, sin teoría de relleno ni diagramas de grafos dirigidos acíclicos.
En este primer post, los tres comandos con los que empiezas todo: `git init`, `git clone` y `git status`. Son los primeros que tecleas en un repo y los últimos que dejas de usar.
## `git init`: convertir una carpeta en un repositorio
`git init` convierte un directorio normal en un repositorio de git. Lo ejecutas dentro de la carpeta que quieres versionar y crea un directorio oculto `.git/` con toda la maquinaria: referencias, objetos, configuración local. El resto del directorio sigue exactamente como estaba.
```bash
mkdir mi-proyecto
cd mi-proyecto
git init
```
A partir de ese momento, git sabe que esa carpeta es un repositorio. No ha trackeado todavía ningún fichero, ni ha hecho commits, ni ha creado la rama principal (aunque la tiene "prevista" como `main` en versiones modernas). Solo ha montado la infraestructura.
Variantes útiles:
```bash
git init -b main # fuerza que la rama inicial se llame main
git init --bare repo.git # repo sin working tree, para usar como remoto
```
La versión `--bare` es la que montas en un servidor cuando quieres un repo central al que la gente pueda hacer push. Un repo normal no está pensado para recibir push a su rama actual; uno bare sí.
Un error típico: ejecutar `git init` dentro de una carpeta que ya es un repo, o peor, dentro de una subcarpeta de un repo existente. No rompe nada, pero te queda un repo anidado con comportamientos raros. Antes de teclear `git init`, verifica con `git status` que no estás ya dentro de un repositorio.
## `git clone`: traerte un repo que ya existe
`git clone` hace una copia local de un repositorio remoto. Es lo que ejecutas cuando alguien te pasa una URL de GitHub, GitLab o un servidor interno:
```bash
git clone https://github.com/usuario/proyecto.git
git clone git@github.com:usuario/proyecto.git
```
La primera forma usa HTTPS, la segunda SSH. Funcionalmente son equivalentes para la mayoría de operaciones, pero SSH es más cómodo cuando vas a hacer push frecuente porque no te pide credenciales cada vez (usa tu clave SSH).
`clone` hace bastantes cosas en un solo comando:
1. Crea una carpeta con el nombre del repo (o el que le pases como segundo argumento).
2. Ejecuta `git init` dentro.
3. Configura `origin` apuntando a la URL desde la que clonaste.
4. Descarga todos los objetos del repo remoto.
5. Hace checkout de la rama principal.
Opciones que uso a menudo:
```bash
git clone --depth 1 URL # clone "shallow", solo el último commit
git clone --branch desarrollo URL # clona y hace checkout de una rama concreta
git clone URL carpeta-destino # clona en una carpeta con otro nombre
```
El `--depth 1` es útil cuando el repo es gigante y solo lo quieres para compilar, no para trabajar en él. Reduce el tamaño descargado muchísimo. Si luego necesitas más historial, siempre puedes hacer `git fetch --unshallow`.
## `git status`: qué tiene mi repo ahora mismo
Si tuviera que quedarme con un solo comando de git, sería este. `git status` te dice en qué rama estás, qué ficheros han cambiado, cuáles están preparados para commit, cuáles tienes sin trackear, y si tu rama está adelantada o atrasada respecto al remoto.
```bash
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add
..." to update what will be committed)
(use "git restore ..." to discard changes in working directory)
modified: README.md
Untracked files:
(use "git add ..." to include in what will be committed)
notas.txt
no changes added to commit (present, use "git add" -p to track)
```
Git te da pistas dentro del propio output: "use git add", "use git restore". Léelas. Git está diseñado para enseñarte los siguientes pasos si prestas atención a lo que te dice.
Variantes que uso:
```bash
git status -s # versión corta, una línea por fichero
git status -sb # corta + muestra el estado de la rama
```
La versión `-sb` es la que acabas usando cuando el output por defecto te parece demasiado verboso. Con dos caracteres por línea te dice todo: `??` es untracked, `M` modificado, `A` añadido, `D` borrado. La primera columna es el staging area, la segunda es el working tree.
## El flujo mental básico
Con estos tres comandos ya tienes el punto de partida:
1. O empiezas un proyecto nuevo con `git init`, o te bajas uno existente con `git clone`.
2. Trabajas en los ficheros.
3. Cada cierto tiempo, lanzas `git status` para ver qué has tocado.
El hábito de lanzar `git status` constantemente es lo que diferencia a quien se maneja bien con git de quien teclea comandos a ciegas. En una sesión normal, yo ejecuto `git status` probablemente cincuenta veces. Antes de cualquier operación que no sea trivial, verifico el estado.
## Errores típicos cuando empiezas
**Clonar dentro de otro repo.** Si estás en tu carpeta `~/proyectos/trabajo/` y haces `git clone` allí, el nuevo repo queda dentro de cualquier otro repo que haya en una carpeta padre. Confuso y peligroso. `git status` te salva aquí también.
**Olvidar que `git init` no añade ficheros.** Iniciar un repo no trackea nada. Tienes que añadir ficheros con `git add` y hacer commit. Lo vemos en el siguiente post.
**Clonar con HTTPS cuando necesitas push frecuente.** Si cada push te pide contraseña, cambia a SSH. Actualizar la URL del remote se hace con `git remote set-url origin git@...`.
## Lo que viene
Con el repo creado y el hábito de consultar su estado, el siguiente paso es guardar cambios. En la **[segunda entrega de la serie](/search?tag=git-basico)** toca el ciclo básico: `git add`, `git commit` y `git diff`. Es decir, cómo preparas un commit, cómo lo creas y cómo revisas lo que estás a punto de guardar antes de hacerlo.
Si ya dominas lo básico, puedes saltar directamente a la serie **[intermedia](/search?tag=git-intermedio)** (ramas, deshacer y stash) o a la **[avanzada](/search?tag=git-avanzado)** (rebase, bisect, worktree).
---
# Reunirse sin motivo: el arte de la quedada porque sí
URL: https://javiervalencia.net/post/reunirse-sin-motivo
En la vida adulta casi todas las quedadas con amigos tienen un motivo: un cumpleaños, una despedida de soltero, una boda, una cena por alguien que está de paso. Los motivos facilitan organizar: hay una razón, una fecha, un plan. Pero se pagan con un coste que casi nadie ve: las quedadas con motivo son siempre sobre algo, y ese algo absorbe la mayor parte de la atención. Reunirse sin motivo es raro, más valioso, y cada vez más difícil de conseguir. Este post es mi defensa de la quedada porque sí.

## Qué es una quedada sin motivo
Una quedada sin motivo es reunirse con amigos porque quieres verles, no porque haya que celebrar algo. El jueves una cerveza en la misma terraza de siempre, cuatro personas, nada que contar más allá de lo ordinario. Una cena un domingo en casa sin ser el cumpleaños de nadie. Un café un sábado por la mañana solo porque os apetecía.
El rasgo distintivo es que, si alguien te preguntara por qué te reunisteis, la única respuesta honesta sería "porque nos apetecía". No hay justificación social, no hay evento que documentar, no hay razón utilitaria. Estás con los amigos por estar con los amigos. Punto.
## Por qué ha desaparecido
Las quedadas sin motivo han ido desapareciendo por varias razones concretas:
**La agenda adulta tiene cada vez más eventos con motivo.** Cuando tienes trabajo, familia, amigos con hijos, círculos superpuestos, los fines de semana se llenan de cumpleaños, bodas, comuniones, despedidas. Para el año que llega lleno de eventos, meter además quedadas sin justificación parece excesivo.
**La logística del encuentro exige un pretexto.** Si quedamos por el cumpleaños de Marta, todos entendemos por qué hay que organizarlo. Si queremos quedar sin nada, hay que convencer al grupo de que la quedada sin justificación merece un sábado por la noche. Es una venta más difícil.
**La cultura de "sacar algo" de cada plan.** Vivimos en un tiempo donde se espera que las actividades "rindan": una experiencia nueva, un recuerdo memorable, una foto. Una quedada sin motivo no rinde en este sentido. Es deliberadamente ordinaria. Va contra el espíritu del tiempo.
**La culpa del tiempo "mal usado".** Dedicar tres horas un sábado a estar con amigos sin hacer nada especial puede dar la sensación de que estás desaprovechando el tiempo. Esa sensación es la que mata las quedadas sin motivo antes de que ocurran.
## Por qué siguen siendo importantes
Y sin embargo, las quedadas sin motivo cumplen una función que las quedadas con motivo no cumplen:
**Mantienen la relación al día.** En un cumpleaños hay muchas personas, conversaciones dispersas, ruido, poco tiempo con cada uno. En una quedada de cuatro personas sin motivo, hablas realmente con cada uno. Os enteráis de cómo están, os apoyáis en cosas pequeñas, os reís de tonterías.
**Permiten conversaciones difíciles.** Los temas importantes rara vez aparecen en una celebración. Aparecen en las tardes sin agenda, cuando la conversación puede ir adonde quiera. Muchas cosas importantes que sé de mis amigos las sé por quedadas sin motivo, no por eventos grandes.
**Son la prueba de que te importan sin necesidad de pretexto.** Reservar un sábado para estar con alguien sin que haya razón obvia es una declaración silenciosa de valor. Les estás diciendo: "me importas sin que tenga que ser tu cumpleaños". Y eso se nota.
**Generan memoria cotidiana.** Las celebraciones se recuerdan en bloque ("el cumpleaños de Juan el año pasado"). Las quedadas sin motivo producen recuerdos de pequeños momentos: una frase, una risa, una discusión que no se resolvió. Son memoria diaria, no memoria fotográfica.
## La forma que funcionan mejor
He experimentado con varios formatos. Los que mejor funcionan para quedadas sin motivo tienen características concretas:
**Número pequeño.** Tres o cuatro personas. Por encima de cinco, la quedada requiere logística y empieza a parecerse a un evento.
**Sitio familiar.** El bar de siempre, la terraza de alguien, la misma cafetería. La quedada sin motivo se apoya en la repetición del lugar. No hay que decidir nada.
**Horario no festivo.** Un miércoles por la noche, un domingo al mediodía, un sábado por la tarde. Horarios que permiten estar relajado sin comprometer otras cosas importantes.
**Duración acotada.** Una o dos horas. No es una cena larga. Es una visita. Entras, estás un rato, te vas. Puede repetirse la semana siguiente sin agotar al grupo.
**Sin fotos.** La foto convierte la quedada en evento. Una foto para el grupo puede estar bien, pero no más. La quedada sin motivo no quiere convertirse en contenido.

## Cómo sostenerlas
La única forma que he encontrado de que las quedadas sin motivo ocurran con regularidad es ritualizarlas. Convertirlas en automáticas, sin que nadie tenga que proponerlas:
Con un grupo de amigos, la rutina es: cañas los jueves a las ocho, siempre en el mismo bar. Quien puede, va. Quien no, no. No hay mensaje previo que diga "¿vamos hoy?". Se asume que va.
Con otro grupo: café los sábados a las once. Mismo formato. Misma regla implícita.
Con estos dos rituales, tengo garantizadas unas diez horas al mes con amigos sin motivo, sin esfuerzo logístico, sin que dependan de que nadie tenga iniciativa en una semana concreta. Es el sistema que he encontrado para compensar el deterioro natural que las agendas adultas generan.
## Lo que no es una quedada sin motivo
Hay formatos que parecen quedada sin motivo pero que no lo son:
**Una reunión de trabajo informal.** Aunque os veáis en una cafetería y no haya agenda, estáis ahí por el trabajo. Es otro contrato.
**Una comida familiar sin ocasión.** Con la familia hay otra dinámica. La obligación implícita de la familia cambia el carácter de la reunión.
**Una quedada donde uno la usa para contarte algo importante.** Si alguien te pide quedar y luego tiene una noticia (cambio de trabajo, ruptura), la quedada tenía motivo, solo que no lo sabías al principio. Es legítima pero no es sin motivo.
La quedada sin motivo requiere, literalmente, ausencia de propósito. Si hay algo que vertebra la reunión más allá de "vernos", ya no es sin motivo.
## La conversación que no se da en eventos
Hay un tipo concreto de conversación que solo ocurre en quedadas sin motivo. Empieza con una tontería: algo que alguien vio, un recuerdo, una anécdota del trabajo. De ahí la conversación deriva, y en media hora estás hablando de algo profundo sin haberlo planeado.
En una boda no pasa esto. Hay demasiada gente, demasiado ruido, demasiada performance social. En un cumpleaños tampoco. Pasa en el bar un jueves con dos amigos y una caña en la mano. La estructura laxa de la quedada permite que las conversaciones encuentren su propio nivel.
Este tipo de conversación espontánea es el combustible de las amistades profundas. Sin ellas, las amistades se mantienen superficialmente pero no crecen. Con ellas, las amistades se renuevan constantemente.
## El coste de no tenerlas
Un síntoma muy específico de una amistad que ha perdido vida: todas las quedadas son con motivo. Solo os veis en cumpleaños, bodas y despedidas. Nunca "porque sí". Si llevas un año sin ver a alguien fuera de eventos, la relación está entrando en modo social amplio: no está muerta, pero tampoco está viva.
Esto es importante detectarlo, porque las amistades pueden descender lentamente al nivel "solo eventos" y desde ahí caer al olvido sin que lo notes. La quedada sin motivo es el indicador y también el remedio: si la propones y ocurre, la relación sigue viva; si la propones y nunca encuentra hueco, es una señal.
## Para probar esta semana
Si llevas tiempo sin quedar con alguien sin motivo, prueba esta semana. Escribe a un amigo. No propongas nada concreto: propón simplemente "¿quedamos a tomar una cerveza esta semana?". Sin aniversario, sin despedida, sin cena con plato contratado. Solo "ver qué tal".
Si el amigo es buen amigo, te va a contestar sí. Si no es tan buen amigo, igual te pide justificación ("¿algo que celebrar?"). Ambas respuestas te dicen algo útil sobre la relación.
Cuando quedéis, haz lo que sea más fácil: el bar de siempre, una hora, una caña, conversación libre. Cuando te vayas, ni fotos ni reportes. Solo el gusto de haber quedado porque sí.
## Para cerrar
En una vida adulta donde casi todo tiene motivo, proteger el espacio de lo sin motivo es protegerse a uno mismo. La amistad sin propósito es la única que aguanta décadas sin desgaste, porque no depende de eventos que celebrar ni de favores que devolver. Se sostiene sola, por el simple gusto de estar con quien te importa.
Reunirse sin motivo no es un lujo. Es la forma más básica de amistad. Y sin embargo, en 2026, es una de las cosas más raras que veo en mi entorno. Volverla a la normalidad empieza, como casi todo, con proponerla uno mismo.
---
# Comer kilómetro cero: productores y fincas de Málaga
URL: https://javiervalencia.net/post/comer-kilometro-cero-en-malaga
El "kilómetro cero" se ha convertido en una etiqueta con mucho marketing y poco contenido. En las cartas de algunos restaurantes significa solo "lo que pudimos comprar cerca esta semana". En las estanterías de algunas tiendas significa solo "producido en la misma provincia". La versión seria del concepto, la que a mí me importa, es otra cosa: conocer al productor, saber cómo cultiva, comprar directamente o a través de un canal corto, pagar un precio justo por producto de calidad. Este post recorre los productores y tiendas de la provincia de Málaga donde el kilómetro cero significa algo de verdad.

## Aceite: la cooperativa de Benamargosa
Si hay un producto insignia de la provincia, es el aceite de oliva virgen extra. Málaga tiene varios molinos de primer nivel, pero uno al que llevo años yendo es la cooperativa de Benamargosa, en la Axarquía.
Cooperativa de pequeños agricultores. Aceituna verdial (variedad local) cogida manualmente a primera hora de la mañana. Molturación en frío el mismo día de la recogida. El aceite sale con un amargor elegante y una pimienta al final que es la firma de los aceites buenos de esta zona.
**Cómo llegar**: treinta y cinco minutos desde Málaga capital, por la carretera de Vélez-Málaga. La cooperativa se visita con cita previa. Te enseñan el molino, te explican el proceso, te ofrecen cata.
**Precio**: alrededor de siete euros el litro en botella de cristal. Los dos litros en lata salen mejor de precio. Es tres veces el precio del aceite de supermercado, y es ocho veces el producto.
**Temporada**: cosecha en noviembre-diciembre, aceite nuevo en venta desde enero. Antes de febrero, es el aceite mejor. A partir de verano, el aceite va perdiendo frescura; en otoño ya hay que esperar a la nueva campaña.
## Queso: quesería Sierra de Gibraltar
La quesería Sierra de Gibraltar está en el término municipal de Casares. Es una pequeña explotación familiar con un rebaño de doscientas cabras payoyas (raza autóctona en peligro). Producen siete quesos distintos a lo largo del año, todos con leche de sus propias cabras.
**Qué pedir**: el curado de seis meses, que es su producto estrella. Textura cremosa con algunos cristales de tirosina, sabor intenso sin ser agresivo. Catorce euros la cuña de trescientos gramos.
**Cómo llegar**: una hora desde Fuengirola hacia el oeste, por la A-7 y luego la MA-8300. La finca tiene tienda propia los sábados de diez a dos. También distribuyen en las queserías de referencia en Málaga capital.
**Por qué merece la pena**: porque la raza payoya está reduciéndose y comer su queso es una de las pocas formas directas de apoyar su conservación. Y porque el queso es, simplemente, muy bueno.
## Aguacate y mango: las fincas de La Cala del Moral
La costa oriental de Málaga (La Cala, El Rincón de la Victoria, Vélez) es una de las zonas aguacatera y manguera más importantes de Europa. Hay fincas pequeñas por todas partes, pero pocas venden directamente al consumidor.
Mi preferida es Finca La Pimpi, cerca de La Cala del Moral. Producen aguacate hass y manga tommy. Venta directa a domicilio y en mercadillos locales. El aguacate aquí está semanas de árbol recolectado cuando en los supermercados lleva meses en cámara.
**Cómo acceder**: a través de su Instagram o por teléfono. Reparten caja de cinco kilos de aguacates por veinte euros. Salen a menos de la mitad de lo que costarían en Mercadona, con diferencia de calidad.
**Temporada**: aguacate de octubre a marzo. Mango de septiembre a noviembre. Fuera de esas fechas, no hay producto local y lo que se vende viene de Perú o México.

## Verdura: la huerta de Alhaurín
En el valle del Guadalhorce, sobre todo en Alhaurín el Grande y Cártama, sigue habiendo una tradición hortelana pequeña. Fincas de pocas hectáreas que cultivan tomate, pimiento, berenjena, calabacín y frutales de hueso para venta local.
Hay dos cooperativas en Alhaurín el Grande donde puedes comprar directo al productor los sábados por la mañana. No son mercados bonitos para turismo: son cobertizos donde la gente del pueblo va a abastecerse. Precios de origen (tomate raf a dos euros el kilo cuando en supermercado está a cinco), producto del día.
**Recomendación**: ir con tiempo, con dinero en efectivo, y con ganas de comprar más de lo previsto. El tomate raf de Alhaurín en temporada (mayo-septiembre) es uno de los mejores tomates que he probado. Merece el desplazamiento.
## Vino: Dimobe y la revolución del moscatel
Los vinos de Málaga llevan siglos de historia, aunque durante décadas tuvieron mala fama (eran asociados a vinos dulces industriales baratos). En las últimas dos décadas ha habido una revolución tranquila de pequeños productores que han recuperado variedades autóctonas y métodos tradicionales.
Dimobe, en Moclinejo, es uno de los referentes. Hacen vinos de moscatel, pero también tintos de romé y blancos de pedro ximénez seco que son una revelación. Bodega familiar, explotación pequeña, venta directa con cata incluida.
**Qué probar**: el moscatel seco "Zumbral" (sí, moscatel puede ser seco, muy poca gente lo sabe). Catorce euros la botella. Diferente a cualquier vino que se suele asociar con Málaga.
**Cómo llegar**: cuarenta y cinco minutos desde Málaga capital, por la A-356. Cata guiada con cita previa los viernes y sábados.
## Pescado: la lonja de Fuengirola
La lonja de Fuengirola es una de las pocas lonjas activas que quedan en la Costa del Sol. Abre a las cinco de la tarde al público, cuando los barcos descargan. Es un espectáculo que recomiendo ver al menos una vez.
El pescado de la lonja no lo vendes tú directamente (la subasta la hacen los pescaderos), pero las pescaderías de Fuengirola que venden pescado de lonja suelen ser honestas al respecto. Pregunta directamente.
**Cuándo ir**: de lunes a viernes, de cinco a siete de la tarde. Los fines de semana no hay subasta.
**Qué mirar**: el tipo de barcos, los cajones que descargan, las variedades. Te da un conocimiento de producto que luego usas cuando pides en un restaurante: sabes qué es un boquerón de Fuengirola y puedes distinguirlo de uno de importación.
## Producto ecológico: la tienda El Rincón Bio
Para lo que no consigues en productor directo, hay tiendas especializadas en producto ecológico local. El Rincón Bio en Fuengirola trabaja con productores pequeños de la provincia. No tienen todo lo que existe en producto grande, pero lo que tienen es de calidad garantizada.
**Qué pedir**: los huevos de corral de la Axarquía (seis euros la docena), la miel de abejas de la Serranía de Ronda (ocho euros el kilo), el pan de masa madre del horno de Álora (tres euros la hogaza).
**Por qué recomendar**: porque servicios así permiten comer kilómetro cero sin tener que ir a cada productor. La comodidad es parte de que el kilómetro cero sea sostenible en el tiempo.
## Lo que no es kilómetro cero
Para ser precisos, hay cosas que se venden con etiqueta de "local" pero que no lo son realmente:
**Pescado de "la lonja" en restaurantes grandes de turistas.** La lonja local no da para alimentar veinte restaurantes al día. La mayoría del pescado que se sirve como "de lonja" en la Costa del Sol viene de distribuidores regionales que traen pescado de varias lonjas del sur.
**Producto "andaluz" que podría ser de cualquier provincia de Andalucía.** Andalucía es grande. Una patata sevillana, un tomate almeriense o un aceite jaenés son andaluces pero no malagueños. No es necesariamente peor producto, pero no es local en el sentido estricto.
**Carne de cerdo "ibérico de la zona".** La provincia de Málaga tiene cerdo ibérico, pero la mayoría del ibérico que se vende aquí viene de Huelva o Salamanca. Pide denominación concreta.
## La economía del kilómetro cero
Comer kilómetro cero en serio cuesta más dinero y más tiempo. Si tuviera que cuantificar, diría que cuesta entre un 50% y un 100% más que comprar en supermercado, y requiere una dedicación semanal de dos o tres horas para hacer la ruta.
¿Compensa? Depende de tus prioridades. Para mí, que le dedico más dinero del normal a la comida, sí. La diferencia de sabor en algunos productos (aceite, tomate, queso, huevos) es enorme. La trazabilidad me tranquiliza. El apoyo directo a productores locales me parece una forma honesta de usar mi dinero.
Si no te planteas hacerlo todo el tiempo, una alternativa razonable es hacerlo parcialmente: aceite y tomate sí, lo demás del supermercado. Es un 20% del esfuerzo con un 60% del beneficio.
## Para terminar
Comer kilómetro cero no es un fin en sí mismo. Es una forma de relacionarte con la comida que te conecta con quien la produce, con cómo se cultiva, con el ciclo de las estaciones. En una época donde la comida se compra anónima en superficies grandes, con fruta de otros continentes y verduras de invernaderos industriales, mantener un canal corto con productores locales es un pequeño acto con varios beneficios.
Málaga tiene suficiente tradición agrícola y ganadera para que ese canal siga siendo viable. Solo requiere ganas de explorar un poco y un sábado al mes dedicado a hacer la ruta. Si tienes un sábado libre próximamente, te animo a subir al valle del Guadalhorce o a la Axarquía y probar. Vas a volver con la despensa llena y la cabeza un poco más clara sobre lo que te estás comiendo.
---
# Volver a ver a los amigos de toda la vida
URL: https://javiervalencia.net/post/amigos-de-toda-la-vida
Hay una categoría de amigos que la vida adulta, con sus desplazamientos y sus calendarios, vuelve geográficamente lejana pero que siguen siendo, en lo importante, los más cercanos. Son los amigos de toda la vida: los del colegio, los del instituto, los de los primeros trabajos. Los ves dos o tres veces al año si vives en ciudades distintas, y sin embargo cuando te reúnes con ellos tienes la sensación de que el tiempo no ha pasado. Este post es una reflexión sobre por qué esas amistades son tan resilientes, y por qué siguen mereciendo el esfuerzo que cuesta mantenerlas.

## La física extraña de estas amistades
Con los amigos de toda la vida pasa algo que no pasa con ninguna otra relación adulta: el tiempo entre encuentros no es proporcional al deterioro de la relación.
Con un amigo reciente, si pasan seis meses sin veros, la relación se enfría. Hay que reconstruir contexto, actualizar lo que ha pasado, volver a establecer la confianza. El tiempo sin contacto erosiona la cercanía.
Con un amigo de toda la vida, pueden pasar dos años sin veros y cuando os sentáis en un bar la conversación arranca como si os hubierais visto ayer. No hay reconstrucción. No hay incomodidad inicial. Hay reconocimiento inmediato y, en cinco minutos, estáis metidos en lo de siempre.
Esto desafía la intuición de que la amistad requiere contacto constante. Lo requiere en su fase de construcción. Una vez construida con profundidad, opera con reglas distintas. La memoria compartida es tan densa que sostiene la relación durante años sin alimentación.
## Qué tipo de memoria compartida
Los amigos de toda la vida comparten un tipo de memoria que es difícil de explicar a quien no la tiene. No son solo anécdotas (aunque haya muchas). Es un mapa mental compartido.
Sabéis cómo era cada uno a los dieciséis años. Sabéis por qué dice el otro las cosas que dice, porque lo habéis visto evolucionar. Sabéis qué temas tocar con cuidado, qué chistes entenderá, qué referencias culturales son las vuestras. No hay que explicar el contexto de nada: el contexto es décadas de conocimiento mutuo.
Esto tiene un efecto específico: con los amigos de toda la vida puedes ser tú mismo sin filtros en un sentido que con amistades más recientes no puedes. No estás performando una versión de ti. Estás siendo el mismo que ha sido durante treinta años, con todas las contradicciones y todas las manías.
Para una persona adulta, ese espacio de ser-sin-actuar es más escaso de lo que parece.
## Lo que se pierde con la distancia
No todo sobrevive sin cuidados. La distancia sí tiene costes. Con los amigos de toda la vida hay varias cosas que se pierden cuando pasas demasiado tiempo sin coincidir:
**El detalle de su vida diaria.** Sabes que están bien o mal globalmente, pero no sabes cómo les fue la reunión del martes, qué comieron el sábado, qué película vieron anoche. Ese ruido cotidiano, que es el combustible de las amistades cercanas en distancia, lo pierdes.
**Las referencias actuales compartidas.** Viven en un mundo local con sus bares, sus personajes, sus preocupaciones municipales. Tú vives en otro. Cuando hablan de "el alcalde nuevo", no tienes contexto. Cuando tú les hablas del tuyo, ellos tampoco. Es un distanciamiento cultural lento.
**Los pequeños roces constructivos.** Verse seguido produce fricciones menores que se resuelven rápido y refuerzan la relación. Verse tres veces al año impide esos roces, pero también impide su resolución. Las relaciones distantes se mantienen pero no crecen.
## Lo que sobrevive a todo
Lo que sobrevive, en cambio, es lo esencial:
**La confianza.** Sabes que puedes contarles cualquier cosa y la van a tratar bien. Esta confianza no se renueva con llamadas frecuentes. Se acumula con los años y se conserva.
**El reconocimiento profundo.** Os miráis y os reconocéis. Aunque hayas cambiado, aunque hayan cambiado, los ejes fundamentales son los mismos. Esto es una sensación que no es reemplazable.
**El humor compartido.** Las bromas viejas siguen funcionando. Las referencias internas del grupo se mantienen. Hay una gramática del humor que os pertenece solo a vosotros.
**La capacidad de darse consejo.** Porque os conocéis en profundidad, un consejo de un amigo de toda la vida vale más que el de veinte conocidos nuevos. Saben qué estás dispuesto a hacer y qué no, conocen tus patrones, conocen tus valores.
## El formato del reencuentro
Cuando te reúnes con amigos de toda la vida después de meses, hay un formato recurrente que funciona mejor que otros:
**Cena larga el primer día.** Llegáis, os veis, os contáis las grandes cosas: qué ha pasado estos meses, actualizaciones laborales, cambios familiares. Dos o tres horas de "ponerse al día" concentrado.
**Plan más ligero el segundo día.** Un paseo, un aperitivo, visita a un sitio que os gusta. Aquí ya no hay necesidad de actualizar: entráis en conversación normal, con los chistes antiguos reactivados.
**Cierre emocional el último día.** Aunque nadie lo nombre así, el último día de un reencuentro con amigos de toda la vida suele tener un momento más íntimo: una conversación más sincera, una cosa que se contó por fin, un abrazo más largo al despedirse.
Este arco (poner al día, relajarse, cerrar) funciona mejor cuanto más lo respetas. Meter demasiada gente nueva en medio, o reducirlo a una sola noche, lo achata.

## El esfuerzo desigual
Una realidad incómoda sobre los amigos de toda la vida: el esfuerzo de mantenimiento suele caer, sistemáticamente, sobre pocas personas del grupo.
Siempre hay uno o dos que organizan, que llaman, que empujan las quedadas. El resto vamos. Sin ellos, la amistad se diluiría en dos años. Con ellos, se mantiene durante décadas.
Esto no es justo pero es así. Y si has sido ese amigo mantenedor, sabrás que a veces agota. Ayudarles un poco (responder pronto, confirmar, no cancelar) es la manera mínima de devolver el esfuerzo invisible que hacen por el grupo.
Yo he sido durante años de los que recibe y no de los que organiza. En los últimos tiempos estoy intentando compensar. No es por justicia abstracta: es porque valoro el grupo y quiero contribuir a su conservación.
## La economía afectiva
Desde una óptica cínica, los amigos de toda la vida son una inversión a muy largo plazo con rendimientos enormes.
Inversión: cinco o diez años de cercanía intensa en la juventud, cuando no cuesta. Luego, contacto esporádico.
Rendimiento: acceso durante las siguientes cinco o seis décadas a un grupo de personas que te conocen profundamente, en quienes confías plenamente, con quienes puedes hablar de lo que sea, sin trabajo adicional.
Ninguna otra relación adulta tiene esa relación costo/beneficio. Las amistades nuevas requieren mucho trabajo para llegar a una profundidad parecida, y rara vez la alcanzan. La pareja requiere una dedicación continua. La familia política es un caso aparte.
Los amigos de la juventud son una infraestructura afectiva que construimos cuando no sabíamos que la estábamos construyendo. Por eso los deberíamos cuidar con conciencia ahora.
## Las pérdidas que no se reemplazan
Hay amigos de toda la vida que he perdido: uno por una distancia que se fue ampliando sin drama hasta que nos dejamos de llamar; otro por un desencuentro que nadie supo resolver; un tercero por el simple hecho de que murió antes de tiempo.
Cada una de esas pérdidas ha sido difícil de reemplazar. No porque no haya gente nueva en mi vida, sino porque lo que aquellos amigos ocupaban no es una función que se reemplace: es un lugar concreto, con una densidad específica, en una geografía personal.
La conclusión que he ido sacando de esas pérdidas es simple: los amigos vivos, presentes, con los que aún tienes relación, son algo que no se puede reponer si se pierden. Invertir en sostenerlos es matemáticamente rentable aunque a veces cueste.
## Para los reencuentros próximos
Si tienes pendientes amigos de toda la vida a los que hace meses que no ves, mi recomendación es sencilla: pasa a la acción. No esperes a que surja. Propón tú la fecha. Ofrece tu casa si puedes. Compra los billetes. Bloquea el fin de semana en el calendario.
Los reencuentros no se auto-organizan. Cuestan un pequeño esfuerzo logístico. Ese esfuerzo es, de lejos, la mejor inversión afectiva que vas a hacer este año. Lo digo sin exagerar, basándome en lo que he visto y vivido.
## Para cerrar
Los amigos de toda la vida son uno de los pocos regalos gratuitos de la vida adulta: relaciones profundas que se construyeron cuando no costaban y que siguen rindiendo dividendos décadas después. Mantenerlas vivas exige pequeños gestos pero no es un trabajo mayor. Lo único que hace falta es conciencia de que están ahí y decisión de verles dos o tres veces al año.
Si hace más de seis meses que no ves a un amigo que llevas contigo desde los quince años, es el momento de llamarlo. No mañana: hoy.
---
# Mi top de restaurantes en la Costa del Sol
URL: https://javiervalencia.net/post/top-restaurantes-costa-del-sol
Después de doce años viviendo en la Costa del Sol, con cientos de comidas en decenas de sitios, hay un puñado de restaurantes a los que sigo volviendo. No son necesariamente los más famosos, ni los que tienen estrella, ni los que salen más en las guías. Son los que, para mí, representan algo que merece la pena defender: cocina honesta, producto bueno, precios razonables para lo que se come, y ese algo indefinible que hace que un restaurante sea "el tuyo". Este post es mi top personal, con el plato que siempre pido y por qué cada uno me sigue trayendo de vuelta.

## Bodega El Gallo (Mijas Pueblo)
Un bar pequeño, apartado, que encontré casi por casualidad hace ocho años. La carta son cuatro cosas. El producto es local en un 95%. La cocina la hace la dueña desde hace treinta años, y su hijo lleva la sala.
**Lo que siempre pido**: huevos rotos con jamón de la sierra y patata confitada. Es la prueba de cualquier cocina honesta: con tres ingredientes, hacer un plato que recuerdas. Aquí la yema es amarilla intensa (huevos de corral de un vecino), la patata está al punto (ni dura ni deshecha), el jamón es ibérico de bellota y está en lonchas anchas con la grasa correcta. Doce euros. Para mí, irreprochable.
**Por qué vuelvo**: porque la dueña me conoce. Porque sé que el cordero segureño de la pizarra es de verdad segureño, no un nombre de mercadotecnia. Porque los precios no han subido de forma demencial. Porque el fin del postre siempre viene con una copa de vino dulce de la casa.
## Venta El Tajo (Ronda, técnicamente)
Ronda está a una hora y media de aquí. Técnicamente no es Costa del Sol, pero es la ruta gastronómica que más hacemos para visitantes. La Venta El Tajo está a cinco kilómetros de Ronda en dirección Setenil. Cocina de caza y carne a la parrilla de leña.
**Lo que siempre pido**: solomillo de ciervo con salsa de cerezas del valle del Jerte. Los ciervos son de monterías de la sierra. La carne viene envejecida quince días. La salsa de cerezas es del propio restaurante, la hacen cada año cuando es temporada y la enlatan. Veintiocho euros.
**Por qué vuelvo**: porque la chimenea está encendida de noviembre a marzo. Porque el dueño te cuenta cada plato como si lo hiciera por primera vez. Porque la carta cambia con las estaciones (no hay caza en mayo, no hay cerezas en noviembre) y eso fuerza a volver varias veces al año.
## Ristorante Da Bruno (Marbella)
Puede parecer raro incluir un italiano en un top de la Costa del Sol, pero Da Bruno es un caso especial: lleva más de treinta años abierto, lo abrió un italiano de Emilia-Romagna que se quedó por aquí, y sigue haciendo pasta fresca todos los días en la misma cocina con los mismos métodos.
**Lo que siempre pido**: tagliatelle al ragú boloñés. El ragú se hace con carne de ternera y cerdo, tomate pelado a mano, vino tinto, tres horas de cocción. La pasta es fresca, hecha esa mañana. El queso parmesano es Parmigiano Reggiano de 24 meses. Dieciocho euros.
**Por qué vuelvo**: porque la pasta fresca hecha en el sitio es otra cosa. Porque Bruno, el dueño original, sigue saliendo de la cocina a saludar. Porque es el único sitio donde he comido unos tagliatelle que me hicieron acordarme de los que comí en Bolonia hace quince años.
## Chiringuito El Barco (Fuengirola)
Lo he mencionado en otros posts sobre chiringuitos, pero merece entrar en el top general. Espetos, pescado frito, un sitio donde el aceite de la freidora no miente y el pescado es de lonja. La terraza con acceso directo a la arena. Cincuenta años en el mismo sitio.
**Lo que siempre pido**: espetos de sardinas (seis por caña, quince euros) y boquerones en vinagre caseros (seis euros la media ración). Nada sofisticado. Lo básico hecho bien.

**Por qué vuelvo**: porque los chiringuitos serios son cada vez menos. Porque sentarse en la terraza a las dos y media de un sábado de primavera, pedir espetos, tomarse una caña, ver el mar, es una experiencia en la que no puedo mejorar. Y porque el precio, pese a las subidas, sigue siendo razonable.
## Taberna El Jardín (Málaga)
En el centro de Málaga, en una calle pequeña detrás del mercado de Atarazanas. Cocina andaluza moderna sin pretensiones. Producto de mercado, cambio de carta semanal, precios medios. Reservar con dos días, más los fines de semana.
**Lo que siempre pido**: tataki de atún rojo de almadraba con vinagreta de alga. Cuando no hay atún rojo, cualquier pescado con la vinagreta del día. El nivel de técnica es claramente mayor que en cualquiera de los otros sitios de mi top, pero no es pretencioso. Es cocina moderna sobre producto excepcional. Veinticuatro euros.
**Por qué vuelvo**: porque me gusta que exista un restaurante en Málaga donde la cocina tenga ambición técnica sin renunciar al producto local. Porque el chef es joven, formado en buenos restaurantes, y ha decidido hacer algo personal en lugar de buscar la estrella. Porque es una prueba viva de que la Costa del Sol tiene cocina contemporánea sin necesidad de irse a Barcelona o Madrid.
## Los criterios
Los cinco restaurantes de arriba comparten cosas específicas que me importan. Aquí están los criterios por los que elegí este top:
**Continuidad en el tiempo.** Todos llevan abiertos más de cinco años. La mayoría más de quince. En hostelería, durar es la señal más clara de que algo funciona.
**Producto honesto.** Todos trabajan con producto de la zona o con producto importado pero trazable. En ninguno te dan pescado congelado pasándolo como fresco ni carne anónima etiquetada como ibérica.
**Precio proporcional a la calidad.** No son los más baratos, pero no son caros injustificadamente. Con veinte a treinta euros por persona comes bien en cada uno. En Málaga capital o Ronda hay restaurantes más caros que comen peor.
**Dueños presentes.** Todos tienen dueños que están en el sitio, no empresarios remotos con gestores. La cocina de un restaurante depende de quién toma las decisiones el día a día.
**Algo propio.** Cada uno tiene una personalidad clara: no son clones de un concepto de moda. El Gallo es de pueblo, Da Bruno es italiano auténtico, el Chiringuito es chiringuito. No intentan ser lo que no son.
## Lo que queda fuera
Hay sitios que me han encantado en visitas puntuales pero que no incluyo en el top porque no he hecho repetición suficiente, o porque son tan exclusivos que no forman parte de mi vida cotidiana. Algunos:
- **Los dos estrellas Michelín de Málaga**. Magníficos, pero 150 euros por cabeza. No son parte de mi dieta regular.
- **Los nuevos restaurantes con carta "de autor"** que abren cada seis meses en la Costa del Sol. Algunos son muy buenos, pero necesito dos o tres años para ver si el concepto se sostiene. Hasta entonces, no los meto.
- **Los restaurantes de hotel**. Hay alguno decente, pero por principio prefiero restaurantes independientes.
## El restaurante perfecto no existe
No hay restaurante perfecto. En mi top hay cosas que no perdonaría en otro contexto: en Da Bruno, el ambiente puede ser ruidoso los sábados. En El Barco, el servicio va lento en temporada alta. En Taberna El Jardín, los precios empiezan a subir a un ritmo que me preocupa.
Pero los defectos son asumibles porque las virtudes son altas y reales. No busco restaurantes sin errores: busco restaurantes donde las virtudes importen más que los errores. Y los cinco del top cumplen esa condición.
## Para quien viene a visitar
Si tuviera que recomendar uno a alguien que viene a la Costa del Sol por primera vez y solo tiene tiempo para un restaurante, sería El Gallo en Mijas pueblo. Es el que mejor resume el espíritu de la zona: pueblo pequeño, producto local, sencillez, honestidad. Los demás son variaciones sobre el mismo tema.
## Para cerrar
Tener un top de restaurantes personales es casi una declaración de principios. Dice qué valoras de un sitio, qué estás dispuesto a perdonar y qué no, cuánto estás dispuesto a gastar y por qué. Los cinco de mi lista no son el mejor de la Costa del Sol en ningún ranking objetivo. Son mis cinco: los que me representan, los que me devuelven una comida bien hecha con gente que respeto, los que siguen sorprendiéndome aunque los conozca ya.
Si pasas por la zona, alguno te va a encantar. Cuéntame cuál.
---
# Conversaciones largas, sin reloj: la sobremesa como lujo
URL: https://javiervalencia.net/post/conversaciones-largas-sin-reloj
Hay un formato de conversación que en los últimos años me he dado cuenta de que es escaso, y que cada vez que lo consigo me deja la sensación de haber tocado algo importante: la sobremesa larga. Una comida que acaba, los platos se retiran, el café llega y no se va a ningún sitio. Tres personas, cuatro, cinco. Dos horas después seguís allí, con una copa de algo, hablando de cosas que no eran el tema de la comida y que están siendo lo mejor del día. Este post es sobre por qué defiendo ese formato con insistencia, y sobre las condiciones que hacen posible que exista.

## Qué hace especial a la sobremesa
Una sobremesa de verdad, de esas que duran hasta las cinco o las seis de la tarde, tiene varias características que la hacen distinta de cualquier otra forma de conversación.
**No hay ceremonia.** Ya habéis comido, los platos ya se fueron. No hay que decidir qué pedir, no hay que organizar el servicio. Estáis sentados sin nada que hacer salvo hablar. Esta es una condición rarísima en la vida adulta.
**No hay agenda.** Nadie ha venido "a hablar de algo concreto". La conversación va adonde va. Puede empezar con una anécdota, saltar a política, volver a recuerdos de infancia, detenerse en algo del trabajo, derivar a un libro. Es una conversación sin rumbo que es, paradójicamente, las mejores que he tenido.
**El tiempo no cuenta.** Si la sobremesa va bien, el reloj no se consulta. Cuando alguien mira el reloj y dice "qué tarde es", la sobremesa ha terminado. Antes de eso, el tiempo está suspendido. Esta suspensión del tiempo es lo que permite todo lo demás.
## Las condiciones que la hacen posible
Las sobremesas largas no ocurren por azar. Hay varias condiciones previas sin las cuales no se producen:
**No tener compromiso inmediato después.** Si alguien tiene que irse a las cinco, se va a ir a las cinco. Esa conciencia del final corta la conversación antes de tiempo. Las mejores sobremesas empiezan el domingo a las tres y acaban cuando el sol se pone.
**Un sitio cómodo donde nadie te echa.** Un restaurante pequeño donde el dueño no tenga prisa, una casa, una terraza en casa de alguien. Los restaurantes grandes con turnos de mesa matan la sobremesa: cuando notas que quieren rotar la mesa, te vas.
**El número adecuado de personas.** Tres, cuatro o cinco. Por debajo, la conversación es menos rica. Por encima, se fragmenta. Con cuatro suele salir la mejor dinámica: todos podéis participar, nadie queda fuera, hay suficiente diversidad de puntos de vista.
**Vino o algo equivalente.** No para emborracharse. Para tener algo en la mano, para que el servicio tenga algún ritmo, para que las sorbas marquen silencios cómodos. Una sobremesa con la mesa vacía es físicamente incómoda. Con una botella de vino a medio terminar, la mesa sigue viva.
## Lo que descubres en ellas
Llevo haciendo ejercicio de memoria con las sobremesas de los últimos años, y algunas conclusiones me repiten:
Las cosas importantes sobre la vida de los demás casi siempre salen en sobremesa. En la comida, durante el plato, hablas de lo que está pasando esta semana: trabajo, niños, viaje pendiente. En la sobremesa sale lo de fondo: la duda sobre si cambiar de ciudad, la preocupación con un padre mayor, la sensación de estar estancado, el proyecto secreto que no te atreves a contar.
Esto no es casualidad. La sobremesa es el momento en que las cañerías se abren porque la estructura se ha disuelto. No hay plato que lleva al siguiente plato. Hay tiempo libre, hay gente conocida, hay seguridad emocional. En ese marco, las cosas importantes emergen por sí solas.

También pasa que cambias de opinión durante la sobremesa. Empiezas con una postura clara sobre algo, alguien te lanza un contrapunto, lo defiendes, surge otro ángulo, y al cabo de cuarenta minutos te das cuenta de que tu postura inicial era más débil de lo que pensabas. Este tipo de revisión de ideas solo funciona en una conversación sin reloj, donde puedes permitirte pensar con gente en tiempo real.
## Por qué escasea
La sobremesa larga escasea por razones concretas:
**Los horarios de los restaurantes modernos.** Muchos restaurantes tienen dos turnos: el de mediodía acaba a las cinco porque preparan cenas. Te hacen sentir en deuda si sigues sentado más allá. Los sitios donde antes te dejaban estar toda la tarde cada vez son menos.
**La cultura del fin de semana lleno.** El sábado y el domingo están cada vez más llenos de compromisos: cumpleaños, planes con los niños, recados pendientes, cena por la noche. Dedicar media tarde a una sobremesa sin otro plan después es un lujo temporal que mucha gente ya no se permite.
**La presión de "hacer algo".** La sobremesa es, literalmente, no hacer nada. Está hablando. Está estar. En una cultura donde el tiempo tiene que "producir" algo (una experiencia fotografiable, una salida, un avance), quedarse sentado dos horas charlando puede verse como tiempo perdido. Obviamente no lo es, pero esa percepción cultural pesa.
**Los móviles.** Hace diez años la sobremesa era más fácil porque no había la tentación constante de consultar el móvil. Ahora, basta que uno saque el teléfono para que la atención del grupo se rompa. Las sobremesas buenas requieren una norma implícita: móvil fuera de la mesa.
## Cómo intento sostenerlas
Algunas cosas que he empezado a hacer para que las sobremesas largas sigan ocurriendo:
**Elegir los sitios con intención.** Hay dos o tres restaurantes pequeños en la zona donde sé que no van a pedirte la mesa. Ahí es donde quedamos cuando queremos una sobremesa de verdad. Pagar treinta euros por persona y quedarte cuatro horas es una inversión razonable.
**Quedar en casa a veces.** Las mejores sobremesas de mi vida reciente han sido en casa de amigos, o en la mía, con comida casera. Sin reloj del camarero. Sin turno de mesa. Puedes estar hasta las nueve de la noche si quieres.
**Proteger el tiempo posterior.** Cuando tengo un domingo de sobremesa con amigos, no pongo nada después. Si surge algo, lo rechazo. La sobremesa solo funciona si sabes que no te va a interrumpir otra cosa.
**No invitar a demasiada gente.** Cuando una comida se hace con ocho o diez personas, la sobremesa se desintegra: la gente se va en grupos, los que quedan ya no encajan. Mejor cenas pequeñas, con sobremesa posible, que comidas grandes donde la sobremesa muere al tercer postre.
## El equivalente sin comida
A veces la sobremesa ocurre sin comida previa. Un café largo con amigos a las cinco de la tarde. Una cerveza que se convierte en tres. Un encuentro casual en una terraza que dura tres horas. La sobremesa no es exclusiva del final de una comida, aunque la comida es un marco que la facilita.
Lo que importa es el principio: tiempo suficiente, sin agenda, con gente con la que puedes estar callado cómodamente. Cuando esas condiciones se dan, el formato es siempre el mismo.
## Lo que no es sobremesa
Hay formatos que se parecen pero que no son lo mismo:
**Una cena que se alarga**: puede ser agradable pero no es sobremesa. Aún hay platos llegando, aún hay servicio, aún hay una estructura que cumplir.
**Una reunión de trabajo que se extiende**: es otra cosa. La conversación tiene un propósito, aunque se disimule.
**Un chat grupal largo**: aunque sea largo y entre amigos, no es sobremesa. La sobremesa requiere presencia física, cuerpos alrededor de una mesa, silencios físicos que se pueden respetar.
## Para cerrar
La sobremesa larga es uno de los formatos sociales más españoles y más en desuso. Es el opuesto del "quedamos un rato rápido para tomar algo". Es decisión consciente de dar tiempo al tiempo, de dejar que las conversaciones se desarrollen a su ritmo, de permitir que surjan cosas que con prisa nunca saldrían.
Si tienes amigos con los que nunca has hecho sobremesa de verdad, pruébalo. Una comida un domingo, sin plan después. Deja que se estire. No cortes. Cuando el dueño del restaurante pregunte si queréis algo más, di que sí, aunque ya no tengas hambre. Quedaos dos horas más. Y observa qué pasa. En mi experiencia, lo que pasa no se olvida.
---
# Un concierto en grupo: por qué sigue siendo especial
URL: https://javiervalencia.net/post/un-concierto-en-grupo
Hay pocas cosas que junten tantos ingredientes buenos como un concierto con amigos. Una noche fuera de casa, música que te importa, gente que te importa, un rato compartido que no se puede repetir. Desde los veinte hasta ahora he ido a muchos conciertos en grupo, y cada vez que lo hago me reafirmo en la misma idea: es de las mejores formas de pasar tiempo con los tuyos. Este post es sobre por qué, y sobre algunas cosas que he aprendido en el camino.

## La ventaja sobre otros planes
Un concierto tiene una característica que lo hace distinto del resto de planes en grupo: **combina intensidad con tiempo no conversacional**.
En una cena, hablas tres horas seguidas y acabas agotado. En una quedada para el fútbol, las conversaciones son cortas y se cortan con la jugada. En un cumpleaños con mucha gente, no hablas en profundidad con nadie. Un concierto reparte la intensidad de otra manera: cuando suena el grupo, estás absorbido por la música, sin necesidad de hablar. Cuando hay descansos, hablas. La alternancia es lo que lo hace sostenible durante cuatro o cinco horas.
Esto significa que en un concierto con amigos no tienes que mantener el esfuerzo social. La música hace parte del trabajo. Pero estáis juntos. Y experimentáis algo juntos.
## La memoria compartida
Los conciertos generan un tipo de memoria que otros planes no generan. Cuando pasan cinco o diez años, te acuerdas con detalle de conciertos específicos. Te acuerdas de dónde estaban los demás, qué llevaban puesto, qué canción fue la que abrió, cuál fue la última. Las cenas se confunden en el recuerdo; los conciertos no.
Esto tiene una razón neurológica probable: la combinación de estímulo sensorial intenso (volumen, luces, multitud) con emoción (la música que te importa) fija la experiencia en la memoria con más fuerza que una conversación tranquila. Pero sea cual sea la razón, el resultado es que los conciertos se convierten en hitos temporales.
"El concierto de Bruce en Madrid", "cuando fuimos a ver a Ruben Blades en Sevilla", "el festival de verano aquel donde llovió todo el sábado". Son puntos de referencia que vuelven en conversaciones años después. Las cenas no. Por eso invertir en un concierto en grupo rinde más, a largo plazo, que invertir en una cena más.
## Lo que funciona
Tres cosas que he aprendido sobre hacer que un concierto en grupo sea bueno:
**Reducir el grupo, no ampliarlo.** Cuatro o cinco personas es el número ideal. Por encima de seis, el grupo se fragmenta: unos quieren estar delante, otros atrás, unos en la barra. Con cinco os movéis como un bloque y no hay negociación constante.
**Llegar pronto.** Llegar con dos horas de margen antes del concierto cambia la experiencia. Te tomas una cerveza tranquilo en un bar cercano, charláis antes de empezar, entráis sin prisa. Llegar justo a tiempo convierte la experiencia en un recorrido estresado con colas y reservas.
**Elegir conciertos que compartáis de verdad.** Si a uno le gusta muchísimo el grupo y a los demás les da un poco igual, se nota. Lo ideal es cuando todos sois fans del mismo grupo, o al menos todos tenéis ganas auténticas de esa música concreta. El grupo se funde mejor.

## Lo que no funciona
Y tres que he descubierto que no:
**Los festivales grandes con alguien que no está acostumbrado.** Un festival de tres días con acampada, seis escenarios, veinte grupos al día y masas de gente es una experiencia intensa. Con alguien habituado es maravillosa; con alguien que no lo está, es un infierno que no te agradecerá. Conciertos puntuales mejor para mezclar gente.
**Conciertos en sitios lejanos con logística compleja.** Ir a Madrid a un concierto desde aquí implica vuelo/tren/coche, hotel, comida, coordinación. La planificación mata la espontaneidad y añade fricción. Los conciertos cerca (Málaga, Sevilla como mucho) funcionan mucho mejor.
**Intentar conversar durante el concierto.** Es tentador durante una balada lenta o un cambio de grupo gritar al oído de alguien una anécdota. Mala idea: el contexto no lo permite, lo que cuentas se pierde, y has interrumpido al otro la escucha. La conversación es antes, en los descansos y después.
## El concierto con los amigos de siempre
Con mi grupo de amigos de toda la vida, el ritual es claro: vamos a un concierto juntos dos o tres veces al año. Lo planificamos con mucha anticipación porque las entradas de los grupos que nos importan vuelan. Coordinamos calendarios con tres meses de margen.
El día del concierto nos vemos cuatro horas antes en la misma cervecería siempre, tomamos un par de cañas, picamos algo ligero. Pasamos a la sala con tiempo. Durante el concierto coincidimos y divergimos por la sala, pero siempre acabamos juntos al final. Después, cena ligera en algún sitio cerca, una copa, y a casa.
Es un ritual de cuatro o cinco horas que se convierte en uno de los mejores días del trimestre. No falla.
## La música como ancla de generación
Una cosa que me ha pasado con los años es que los conciertos de mis veinte me han conectado con una generación concreta. Vas a ver a un grupo que marcó tus veinte, y alrededor tienes gente de tu edad, con experiencias parecidas, que se emociona con las mismas canciones que te emocionaron a ti cuando tenías veinte.
Esa sensación de "no estoy solo" no la tienes todos los días. Ver a quinientas personas cantar al unísono un estribillo que tú cantabas solo en tu cuarto hace veinticinco años es un momento emocionante. La memoria colectiva existe, y los conciertos son uno de los pocos rituales donde se manifiesta.
Y cuando ese momento lo compartes con tres o cuatro amigos con los que también cantabas esa canción en el coche hace veinticinco años, la experiencia se multiplica. Es algo que solo hacemos cada tanto, pero que no tiene sustituto.

## La economía del concierto
Un concierto de tamaño medio en Málaga o Sevilla ronda los cuarenta o cincuenta euros la entrada. Añade cena y cervezas y sales por setenta u ochenta euros por persona. Cuatro veces al año son trescientos euros. No es poco, pero es la definición de gasto bien empleado.
Comparado con otros gastos recurrentes (una suscripción de streaming, un móvil nuevo antes de tiempo, un fin de semana turístico), cuatro conciertos al año con amigos me generan más recuerdos y más conexión social que casi cualquier alternativa. La relación coste/impacto es excelente.
## Lo que el concierto tiene y la plataforma no
Uno esperaría que en 2026, con streaming y pantallas grandes en casa, el concierto en vivo hubiera perdido peso. Para mí ha sido al revés. Cuanto más escucho música en casa (audífonos, HomePod, streaming), más valoro la experiencia en vivo.
En casa la música es tuya, íntima, con control total. Eso tiene sus virtudes. En un concierto la música es compartida, pública, fuera de tu control. Y es precisamente esa pérdida de control (el volumen que no eliges, la canción que no decides) lo que convierte la experiencia en algo distinto. La música deja de ser tu música privada y se convierte en algo que pasa en un sitio, con gente, en un momento concreto.
Esa cualidad ritual la plataforma nunca la va a tener.
## Para los que llevan tiempo sin ir
Si llevas mucho tiempo sin ir a un concierto con amigos, mi consejo es simple: hay que programarlo como se programa cualquier otra cosa importante. No esperes al concierto perfecto; cualquier grupo que os guste razonablemente a los cuatro ya es suficiente. Lo importante no es el grupo absoluto, es la noche.
Compra las entradas con tiempo. Pon la fecha en el calendario de todos. Trata ese día como trataría una boda: protegido, sin otros compromisos. Llega pronto, bebe moderado, escucha con atención, canta sin vergüenza.
## Para cerrar
Los conciertos con amigos son una de esas cosas que la vida adulta tiende a sacrificar primero "por falta de tiempo" y que conviene defender. No son un capricho adolescente ni una nostalgia de los veinte. Son un formato social que genera algo que ningún otro formato replica: emoción compartida con la música de fondo. En una vida donde casi todo está atomizado en pantallas individuales, eso sigue siendo un lujo raro. Merece la pena programarlo.
---
# El Mercado Central de Fuengirola: tapear donde se compra
URL: https://javiervalencia.net/post/mercado-central-de-fuengirola
Los mercados municipales son algo que los urbanitas de cierta edad valoramos cada vez más a medida que van desapareciendo. Son el reverso de la gran superficie: pocos metros, mucha densidad, producto fresco, conversación, olor. El Mercado Central de Fuengirola es uno de los pocos que todavía funcionan como mercado real en la Costa del Sol, no como un reclamo turístico disfrazado. Este post es una guía personal para sacarle el máximo partido, con puestos favoritos y el plan completo de lo que yo hago un sábado por la mañana.

## El edificio y su historia
El Mercado Central está en pleno centro de Fuengirola, a dos calles del paseo marítimo. Es un edificio de los años cincuenta, con planta cuadrada y techos altos. Durante décadas fue el sitio donde la mitad del pueblo compraba la comida de la semana. Hoy sigue siendo, sorprendentemente, un sitio donde de verdad se compra: pescaderos locales, carniceros de toda la vida, fruterías con género de la Axarquía, charcuterías con producto de la Sierra de Ronda.
Pero lo que ha cambiado es lo más interesante: en los últimos años, varios puestos de la mitad norte del mercado se han convertido en pequeños bares donde se come lo mismo que se vende en los puestos de al lado. El resultado es una fórmula rara en España: tapeas en el mismo sitio donde se compra el producto. La cadena de suministro es corta literalmente.
## La ruta que hago
Llego sobre las diez y media de la mañana. El mercado abre a las ocho, pero antes de las diez todavía está en modo compra y tiene menos ambiente para tapear. De diez a una y media es el momento.
**Primera parada: café en La Marisma**, un puesto convertido en barra que hace el mejor café cortado del mercado. Un cortado y una tostada con aceite de Málaga abren boca. Dos euros con cincuenta.

**Segunda parada: boquerones en Pepe el Gallego**. Es una pescadería reconvertida en barra. La pescadería sigue existiendo: compras en el mostrador de la izquierda. La barra es de la derecha. El boquerón viene directo del mostrador a la freidora, cinco minutos pasan entre compra y plato. Esto es impensable en cualquier restaurante. Una ración con pan, seis euros.
**Tercera parada: jamón en La Bellota**. Una charcutería de siempre que cortan al momento. Aquí se viene a probar antes de comprar, pero también puedes quedarte con la tapa. Un plato de jamón ibérico de bellota de Cortes de la Frontera, una copa de manzanilla. Ocho euros.
**Cuarta parada: vermut en El Rincón del Vermut**. Un puesto nuevo, de hace tres o cuatro años, especializado en vermuts artesanos. Vermut rojo con rodaja de naranja y aceituna, papas aliñadas como acompañamiento. Cuatro euros. Aquí suelo parar la marcha y me tomo quince minutos sentado.
**Quinta parada: postre en el horno**. Antes de salir, el puesto de panadería del fondo tiene unas magdalenas caseras que llevan toda la vida haciendo los mismos. Dos magdalenas y una infusión cierran la mañana.
## Lo que se puede comprar (no tapear)
Pero lo interesante del mercado es que también sirve como mercado real. Un sábado por la mañana, entre tapa y tapa, puedo aprovechar para comprar lo de la semana. Esto es parte del atractivo: es un plan que produce algo tangible, no solo gasto.
**Pescadería La Almadraba**: atún rojo cuando hay, corvina de roca, chocos frescos, gamba blanca de Málaga cuando está en temporada. Tres veces más caro que un supermercado pero comparas el género y entiendes por qué.
**Carnicería Hermanos Pérez**: cordero segureño, pollos de corral, cabrito en Navidad. El carnicero te aconseja qué pedir para qué plato.
**Frutería La Axarquía**: lo mismo que cualquier frutería pero con producto estrictamente de la provincia. Fresas de Almayate, aguacates de La Cala, chirimoyas de Motril, tomate raf cuando hay. Noches de gloria en temporada.
**Quesería Sierra Málaga**: quesos de oveja, cabra y mezcla de la zona. La ración semanal de queso me la llevo de aquí. Vale más que del Mercadona pero con diferencia.
## Por qué sobrevive
Los mercados municipales están en retirada en toda España. La mayoría han muerto o están agonizando, víctimas de las grandes superficies, el comercio electrónico y el cambio de hábitos. El Mercado Central de Fuengirola ha sobrevivido por una combinación de factores que merece la pena entender.
**Ubicación céntrica en un pueblo denso.** Fuengirola es uno de los pueblos con más densidad de España. Eso significa mucha gente viviendo a pocos metros del mercado. Para los que viven en el centro, el mercado es más fácil que ir al supermercado a los extrarradios.
**Ayuntamiento que lo ha protegido.** El ayuntamiento ha mantenido los alquileres razonables, ha hecho inversiones en mantenimiento (aire acondicionado, limpieza) y ha permitido la transición de parte del mercado a zona de tapeo sin desvirtuarlo. Es un caso raro de gestión pública sensata.

**Comunidad de comerciantes que se ha renovado.** Parte de los puestos los han tomado hijos o nietos de los dueños originales. Otros son emprendedores nuevos con una visión distinta (el del vermut, el del pan ecológico). La mezcla mantiene el mercado vivo.
**Un componente turístico sano.** Los turistas llegan al mercado, pero no lo han colonizado. Las tapas no están infladas, los horarios no se han vuelto orientados al turista, el idioma siguen siendo el español. Se puede visitar como visita cultural sin que destruya la función original.
## La comparación con otros formatos
Lo que hace especial al Mercado Central es que combina cosas que normalmente están separadas:
Respecto a un **supermercado**: producto fresco, trato humano, posibilidad de conversar sobre recetas con el carnicero, conocer el origen de lo que compras. Pero sale más caro y hay que ir al horario del mercado, no al tuyo.
Respecto a un **restaurante**: comes sentado en taburetes o de pie, sin mantel, sin servicio sofisticado. La experiencia es más ruidosa, más rápida, más informal. Pero el producto es mejor que en el 80% de restaurantes.
Respecto a una **tienda gourmet**: el producto es de calidad similar, pero el precio es accesible y la experiencia es menos pretenciosa.
Es un híbrido que en teoría no debería funcionar, y que en la práctica es de las mejores experiencias gastronómicas que conozco.
## Lo que he aprendido con el tiempo
Doce años yendo al mercado con más o menos regularidad me han enseñado unas cuantas cosas:
**Ir pronto o ir tarde.** El horario central (de 11 a 13) es el peor: lleno, ruidoso, difícil moverse. De 10 a 11 es mejor. De 13 a 14 también, con la ventaja de que a la una y media los pescaderos hacen precio porque van a cerrar.
**Hablar con los comerciantes.** No es solo educación. Es la forma de enterarte de qué llega fresco ese día, qué está en temporada, qué se recomienda. Los mejores consejos gastronómicos los he recibido en el mercado, no en ningún libro de cocina.
**Llevar bolsa de tela grande.** Los plásticos son muchos, los puestos son varios, y salir del mercado con diez bolsas de plástico es un desastre. Una bolsa reutilizable grande lo soluciona.
**No llevar prisa.** El mercado no funciona con prisa. Si llevas prisa, mejor el supermercado. El mercado tiene su ritmo, con sus esperas, sus saludos, sus "dame un segundo que termino con este señor". Acéptalo o no entres.
## Lo que recomiendo y lo que no
**Recomiendo**:
- Ir un sábado entre las 10 y las 13.
- Llevar en efectivo unos cuarenta euros por persona entre tapeo y compra.
- Dejarte llevar: entrar sin lista cerrada de lo que vas a comprar.
- Hablar con al menos dos comerciantes.
**No recomiendo**:
- Ir los lunes (muchos puestos cierran).
- Ir en agosto (vacaciones, muchos puestos cierran también).
- Ir con prisa.
- Esperar un mercado de diseño tipo La Boquería o San Miguel: este es un mercado de verdad, no un decorado.
## Por qué lo defiendo
El Mercado Central de Fuengirola es uno de los sitios que más me hacen sentir que la Costa del Sol sigue siendo un lugar con alma, no solo una extensión de apartamentos turísticos. Cada sábado que voy estoy votando con mis pies y con mis cuarenta euros por un formato que está en extinción y que merece la pena proteger.
Si un día tienes un sábado libre y estás cerca, pásate. Llévate un bolso. Gasta un par de horas. Sal con comida fresca, olor a pescado en la ropa y la sensación de haber hecho algo pequeño pero real. Es un lujo que en 2026 no todos los pueblos tienen ya.
---
# ClickHouse desde cero (V): producción, replicación y clusters
URL: https://javiervalencia.net/post/clickhouse-desde-cero-v-produccion-replicacion-y-clusters
*Quinta y última entrega de la serie **[ClickHouse desde cero a pro](/search?tag=clickhouse-desde-cero)**. Tiempo de lectura estimado: 14 minutos.*
Llegamos al final. En las entregas anteriores has pasado de arrancar un ClickHouse en Docker ([I](/post/clickhouse-desde-cero-i-instalacion-y-primeros-pasos)) a diseñar tablas con `MergeTree` ([II](/post/clickhouse-desde-cero-ii-tipos-de-datos-y-mergetree)), escribir consultas analíticas serias ([III](/post/clickhouse-desde-cero-iii-consultas-analiticas-en-profundidad)) y pre-agregar con materialized views ([IV](/post/clickhouse-desde-cero-iv-materialized-views-projections-y-ttl)).
Ahora toca ponerlo en producción. Este post cubre cuatro cosas que todo equipo acaba necesitando: **replicación**, **sharding**, **backups** y **monitorización**. No vas a salir de aquí sabiendo operar un cluster de 50 nodos, pero sí con la cabeza puesta para diseñar algo que no se caiga a la primera.
## Cuándo replicar y cuándo sharding
Empieza por entender qué problema resuelve cada cosa. Las decisiones tempranas son las que más duelen si te equivocas.
- **Replicación**: copias de los mismos datos en varios nodos. Resuelve *alta disponibilidad*, *durabilidad* y *escalado de lecturas*. No resuelve el problema de que los datos no te caben en un nodo.
- **Sharding**: particiona los datos entre varios nodos. Resuelve *escalado horizontal*: cada nodo guarda una fracción. No da HA por sí solo; hay que combinar con replicación.
Para la mayoría de proyectos medianos, **2 réplicas sin sharding** es la respuesta correcta. Un ClickHouse con 2 nodos de 64 vCPU y 256 GB RAM aguanta decenas de miles de millones de filas sin despeinarse. No empieces por un cluster de 12 nodos porque *"por si acaso"*.
Si llegas a necesitar sharding, lo sabrás: tu disco no da más, los merges no acaban, las consultas paralelas saturan CPU en un solo nodo.
## ClickHouse Keeper
Históricamente, la replicación en ClickHouse dependía de **ZooKeeper**. Hoy existe **ClickHouse Keeper**, un reemplazo compatible escrito en C++ y distribuido con el propio ClickHouse. Mismo protocolo, menos memoria, menos quebraderos.
Lo necesitas obligatoriamente si vas a usar `ReplicatedMergeTree`. Puedes desplegarlo:
- **Como proceso separado** (recomendado en producción): 3 instancias de Keeper en máquinas distintas para quórum.
- **Dentro de clickhouse-server** (modo embebido): útil para pruebas y clusters pequeños.
Ejemplo de configuración embebida en `/etc/clickhouse-server/config.d/keeper.xml`:
```xml
9181
1
/var/lib/clickhouse/coordination/log
/var/lib/clickhouse/coordination/snapshots
1ch-019234
2ch-029234
3ch-039234
ch-019181
ch-029181
ch-039181
```
Tres instancias es el mínimo razonable. Con dos no hay quórum; con una no hay HA.
Comprueba el estado desde cualquier nodo:
```bash
echo mntr | nc localhost 9181
echo stat | nc localhost 9181
```
## ReplicatedMergeTree
Una vez Keeper está en marcha, cambias `MergeTree` por `ReplicatedMergeTree` y automáticamente tienes replicación multi-master: cualquier réplica acepta escrituras y todas acaban sincronizadas.
```sql
CREATE TABLE blog.pageviews ON CLUSTER my_cluster (
ts DateTime,
user_id UInt64,
path String,
country LowCardinality(String),
duration_ms UInt32
)
ENGINE = ReplicatedMergeTree(
'/clickhouse/tables/{shard}/blog/pageviews',
'{replica}'
)
PARTITION BY toYYYYMM(ts)
ORDER BY (ts, user_id);
```
Dos detalles:
- El primer parámetro es la **ruta en Keeper**. Debe ser única por tabla y **compartida entre todas las réplicas del mismo shard**.
- El segundo es el **nombre de la réplica**, único dentro del shard.
- `{shard}` y `{replica}` son macros resueltas por cada nodo, declaradas en `config.xml` / `macros.xml`.
Archivo `macros.xml` en cada nodo:
```xml
01
ch-01
my_cluster
```
`ON CLUSTER my_cluster` es una comodidad para que la consulta se ejecute en todos los nodos del cluster definido en `remote_servers`. Sin ella, tienes que crear la tabla a mano en cada nodo.
### Inserts y consistencia
Por defecto, un `INSERT` en una réplica devuelve `OK` en cuanto el dato está escrito localmente. La réplica lo propaga de forma asíncrona al resto.
Si tu caso exige que el `INSERT` no retorne hasta que N réplicas tengan el dato:
```sql
SET insert_quorum = 2;
INSERT INTO blog.pageviews ...;
```
Con 3 réplicas, `insert_quorum = 2` da "escritura durable en mayoría" al estilo Raft. Es más lento, pero ninguna escritura confirmada puede perderse al caerse una réplica.
## Sharding con Distributed
Cuando llega el momento del sharding, se trabaja con **dos tablas**:
1. Una **local** (normalmente `ReplicatedMergeTree`) que guarda el trozo correspondiente.
2. Una **Distributed** que es un *puntero* a las locales y presenta una vista única.
```sql
-- En cada nodo
CREATE TABLE blog.pageviews_local ON CLUSTER my_cluster (
ts DateTime,
user_id UInt64,
...
)
ENGINE = ReplicatedMergeTree(...)
ORDER BY (ts, user_id);
-- También en cada nodo
CREATE TABLE blog.pageviews ON CLUSTER my_cluster AS blog.pageviews_local
ENGINE = Distributed(
my_cluster, -- nombre del cluster
blog, -- base de datos
pageviews_local, -- tabla local
cityHash64(user_id) -- clave de sharding
);
```
Cuando consultas `SELECT ... FROM blog.pageviews`, ClickHouse:
1. Contacta con una réplica de cada shard.
2. Les manda la consulta en paralelo.
3. Mergea los resultados.
La clave de sharding determina cómo se distribuyen los datos. Elígela bien: algo con alta cardinalidad (como `user_id`) evita hotspots. No uses `country` porque "España" acabaría inflada en un shard y vacía en el resto.
### remote_servers
Es la definición del cluster, en `config.xml` o en un fichero `.xml` bajo `config.d/`:
```xml
ch-019000
ch-029000
ch-039000
ch-049000
```
Dos shards, dos réplicas cada uno. Total: 4 nodos. Cuando insertas en la Distributed, ClickHouse enruta a un shard concreto según la clave; cuando lees, pregunta a los dos.
## Configuración esencial de producción
Una muestra de cambios típicos respecto a los defaults:
```xml
information
1000M
10
100000000
zstd
3
30000000000
60000000000
20000000000
300
system
toYYYYMM(event_date)
event_date + INTERVAL 30 DAY DELETE
7500
```
Recomendaciones específicas de sistema operativo:
- **Filesystem**: ext4 o XFS. Evita ZFS y btrfs si puedes, hay *caveats*.
- **Huge pages transparentes**: desactiva THP. Degradan rendimiento en cargas grandes.
- **Ulimits**: `nofile` a 262144, `memlock` a `unlimited`.
- **Swap**: desactivado o `swappiness = 1`.
## Monitorización
ClickHouse ya incluye métricas detalladas. Solo hay que exponerlas.
### Endpoint Prometheus nativo
En `config.xml`:
```xml
/metrics
9363
true
true
true
```
Prometheus scrapea `http://nodo:9363/metrics`. Si ya usas [Prometheus y Grafana para servicios pequeños](/post/prometheus-y-grafana-para-servicios-pequenos), encaja igual.
### Tablas de `system` más útiles
```sql
-- ¿Están las réplicas al día?
SELECT database, table, is_leader, absolute_delay, queue_size
FROM system.replicas;
-- Consultas lentas de la última hora
SELECT query, query_duration_ms, read_rows, memory_usage
FROM system.query_log
WHERE event_time >= now() - INTERVAL 1 HOUR
AND type = 'QueryFinish'
ORDER BY query_duration_ms DESC
LIMIT 20;
-- Errores recientes
SELECT name, value, last_error_time, last_error_message
FROM system.errors
WHERE last_error_time >= now() - INTERVAL 1 HOUR;
-- Parts por tabla (alerta si explotan)
SELECT database, table, count() AS parts
FROM system.parts
WHERE active
GROUP BY database, table
ORDER BY parts DESC
LIMIT 20;
-- Mutations en curso (pueden ser largas y pesadas)
SELECT * FROM system.mutations WHERE NOT is_done;
```
Alertas mínimas que deberías tener:
- `absolute_delay` de `system.replicas` > 60 segundos.
- Número de *parts* activas > ~300 por tabla. Señal de inserts mal dimensionados.
- Errores nuevos en `system.errors`.
- Espacio en disco < 20%.
- Uso de memoria > 85% sostenido.
- Latencia p99 de consultas por encima de tu SLO.
## Backups
ClickHouse tiene un comando `BACKUP` incorporado desde la versión 22.8:
```sql
BACKUP TABLE blog.pageviews TO S3('https://bucket.s3.amazonaws.com/backups/pageviews', 'key', 'secret');
BACKUP DATABASE blog TO Disk('backups', 'blog-2026-04-20.zip');
-- Restaurar
RESTORE TABLE blog.pageviews FROM S3(...);
```
Es incremental si el destino ya tiene un backup previo compatible. Para clusters con múltiples shards, necesitas orquestarlo nodo a nodo o usar `BACKUP ON CLUSTER`.
Alternativas clásicas:
- **`clickhouse-backup`** ([Altinity](https://github.com/Altinity/clickhouse-backup)): herramienta dedicada, muy madura, integración S3/GCS/Azure nativa.
- **Snapshots de volumen**: si tienes LVM/ZFS o discos en la nube, un snapshot atómico mientras haces `SYSTEM STOP MERGES` funciona.
Regla de oro: **prueba tus restores**. Un backup sin restore probado es placebo. Si no has restaurado nunca, no tienes backup.
## Retos típicos en producción
Una lista de cosas con las que casi todo el mundo acaba tropezando:
1. **"Too many parts"**. Significa que haces inserts demasiado pequeños o demasiado frecuentes. Agrupa en batches más grandes o usa `Buffer` engine delante.
2. **MVs que explotan en un insert**. Los MVs se ejecutan en el contexto del insert; si el MV falla, el insert falla. Prueba los MVs con datos representativos antes de activarlos en producción.
3. **Queries de usuarios consumiendo toda la memoria**. Usa `max_memory_usage` por perfil de usuario y obliga a que consultas ad hoc vayan por un perfil más restrictivo.
4. **Réplicas que se desincronizan**. Casi siempre es Keeper que no tiene quórum o que tiene latencia alta entre nodos. Mide Keeper.
5. **DDL `ON CLUSTER` que se queda bloqueado**. Usa `distributed_ddl_task_timeout` y monitoriza `system.distributed_ddl_queue`.
6. **Disco lleno tras un merge grande**. Un merge puede necesitar temporalmente 2× el espacio del *part* resultante. Mantén siempre margen.
## Recursos imprescindibles
- Documentación oficial: [clickhouse.com/docs](https://clickhouse.com/docs).
- Blog de ClickHouse, Inc.: artículos técnicos muy buenos sobre internals.
- Repositorio de GitHub [ClickHouse/ClickHouse](https://github.com/ClickHouse/ClickHouse): leer los *issues* enseña más que muchos tutoriales.
- El blog de Altinity sigue siendo una referencia para *how-tos* avanzados.
## Cierre de la serie
En cinco posts hemos recorrido ClickHouse desde instalarlo hasta operarlo en cluster. No hace falta dominarlo todo a la primera: cada proyecto te forzará a profundizar en uno u otro aspecto.
Si hay algo que me llevo de usar ClickHouse en producción durante años es esto: **el 80% del rendimiento viene de decisiones tomadas al crear la tabla**. Tipos adecuados, `ORDER BY` bien pensada, *particionado* sensato y un par de materialized views cubren la inmensa mayoría de los casos. El resto es operación, que es donde este último post intenta poner un poco de base.
Serie completa, por si estás aterrizando:
- [I: instalación y primeros pasos](/post/clickhouse-desde-cero-i-instalacion-y-primeros-pasos)
- [II: tipos de datos y MergeTree](/post/clickhouse-desde-cero-ii-tipos-de-datos-y-mergetree)
- [III: consultas analíticas en profundidad](/post/clickhouse-desde-cero-iii-consultas-analiticas-en-profundidad)
- [IV: materialized views, projections y TTL](/post/clickhouse-desde-cero-iv-materialized-views-projections-y-ttl)
- V: producción, replicación y clusters *(estás aquí)*
Y como lectura complementaria, el post introductorio [ClickHouse para desarrolladores que vienen de PostgreSQL](/post/clickhouse-para-desarrolladores-que-vienen-de-postgresql) sigue siendo útil como resumen en una única página para compartir con el equipo cuando tengas que justificar por qué vais a meter un motor nuevo.
Si has llegado hasta aquí, gracias por el rato. Si tienes dudas concretas sobre diseñar una tabla, migrar desde otro sistema o depurar una consulta lenta, mis DMs siguen abiertos.
---
# Pescaíto en Los Boliches: guía honesta
URL: https://javiervalencia.net/post/pescaito-en-los-boliches
Los Boliches es el barrio marinero de Fuengirola, pegado al límite con Benalmádena por el este. Es donde antiguamente descargaban las barcas y donde, durante décadas, estaban las mejores freidurías de la zona. Hoy en día ha cambiado mucho: se ha vuelto más residencial, más turístico, con una torre de apartamentos cada vez más en cada manzana. Pero si sabes dónde mirar, el pescaíto frito sigue siendo bueno, honesto y a precios razonables. Este post es mi guía personal después de vivir doce años por aquí.

## Qué es el pescaíto frito de verdad
Pescaíto frito, en la Costa del Sol, significa una cosa concreta: pescado pequeño, de la lonja cercana (ideal si es de Fuengirola, válido si es de Málaga), enharinado y frito en abundante aceite caliente. Se sirve con limón y sal gorda, y se come con las manos.
Los pescados clásicos son: boquerón (pequeño, entero), salmonete (mediano, entero), calamar (troceado), chopito (pequeño, entero), puntilla (muy pequeña, en cascada), jurel (mediano, troceado) y pijota (pequeño, la merluza de bebé). La fritura variada es una combinación de tres o cuatro de estos.
El punto crítico es el aceite. Tiene que ser limpio (no reutilizado hasta la muerte), caliente (170-180°C), y de oliva suave o de girasol. Si al llegar el plato el pescado está aceitoso y el papel de debajo es una balsa, el aceite está mal. Si el rebozado está seco pero con un leve dorado, el aceite estaba bien.
## Los tres sitios que no fallan
### La Freiduría del Puerto
No está en el puerto exactamente, está a dos manzanas. Es un bar pequeño, con cuatro mesas dentro y cuatro fuera, que lleva cuarenta años abierto con la misma familia. El padre freía, ahora frían los hijos con el mismo sistema.
La fritura variada pequeña (para uno) sale por doce euros. Trae boquerón, calamar, salmonete y puntilla. Raciones generosas. El boquerón es de la lonja de Fuengirola cuando hay, de Málaga cuando no. El calamar es troceado gordo, no los aros finos industriales. La puntilla es la prueba definitiva de una buena freiduría: hay que freírla muy caliente y muy rápido para que quede crujiente sin aceitarse, y aquí la bordan.
Pide una caña pequeña y una media ración de boquerón en vinagre para ir abriendo boca mientras llega la fritura. Es lo que hacen los que vienen mucho.
### El Faro
Un poco más al oeste, en una calle paralela al paseo. Es más grande que La Freiduría del Puerto, con terraza bastante ancha. La fritura es buena pero lo que me trae aquí es el pescado a la plancha, que no es pescaíto frito sino su primo mayor.
Un pargo a la plancha entero, con ajillo por encima y un vasito de vino blanco de la tierra. Alrededor de veinte euros el pargo y seis el vino. Para dos personas, añadiendo una ensalada mixta y unas patatas al ajillo, te sales por cuarenta euros cada uno con propina. No es barato, pero es una de esas cenas que se recuerdan.
Los domingos cierran. Los lunes también. Ir martes a jueves o domingos en festivo.
### El Caballo
Lo pongo en tercer lugar porque es el más informal. Es un bar de barra con unas pocas mesas fuera, no tiene carta escrita (hay pizarra) y la comida se pide al camarero después de ver lo que hay.
Lo que tienen siempre: boquerones fritos, boquerones en vinagre, ensaladilla y gambas cocidas. Lo que tienen a veces: salmonete, pijota, chopito. El precio es el más bajo de los tres (diez euros por una ración de pescaíto decente) y el ambiente es el más auténtico: mezcla de habituales del barrio, algún turista que ha caído ahí casi por casualidad, el camarero gritando las comandas al cocinero.
Si vas con alguien que no conoce bien la zona, es el sitio para mostrarle cómo es comer en un bar de toda la vida.

## Qué pido siempre y qué no
**Lo que pido siempre:**
- **Boquerones fritos**: son el plato más honesto de una freiduría. Si están bien, es que la cocina funciona. Si están mal, todo lo demás probablemente también.
- **Boquerones en vinagre** (si los tienen hechos en casa): ajo, perejil, aceite bueno, vinagre equilibrado. Es la entrada ideal.
- **Puntilla o chopito** (según disponibilidad): la prueba de fuego del freidor.
- **Ensaladilla rusa** (si es casera): es el descanso del paladar entre platos fritos.
**Lo que nunca pido en freiduría:**
- **Gambas rebozadas**. Si son gambas, plancha o cocidas. Rebozadas ocultan producto malo.
- **Calamar a la romana** (los aros grandes con rebozado grueso). Congelados el 95% de las veces. Mejor el calamar troceado local sin rebozado grueso.
- **Cualquier cosa con salsa**. Si la cocina es buena, la salsa es innecesaria. Si la cocina es mala, la salsa es una tapadera.
- **Paella**. Ya lo he dicho en otro post. La paella en un chiringuito o en una freiduría es un aviso.
## La estacionalidad que casi nadie menciona
Un aspecto que no se habla nunca es que el pescado tiene temporada. El boquerón está mejor en ciertos meses que en otros. Los salmonetes de Málaga tienen sus picos. En Los Boliches, al estar pegados a la lonja de Fuengirola, los sitios serios ajustan la carta a lo que hay.
Preguntar "¿qué es lo fresco hoy?" al camarero tiene dos efectos. El primero es práctico: te dicen lo mejor. El segundo es social: quedas como cliente serio y a partir de ahí te tratan con más atención. Los camareros saben quién pregunta bien y quién no.
**Boquerón**: lo más constante. Lonja tiene casi todo el año.
**Salmonete**: mejor de mayo a septiembre. Fuera de temporada es importado y se nota.
**Pijota**: otoño e invierno.
**Chopito**: primavera y principios de verano.
**Jurel**: todo el año, pero espectacular en verano.
## La pregunta del precio
Un tema que merece su espacio: los precios han subido. Hace diez años, una ración de pescaíto frito bien servida costaba seis o siete euros. Hoy, en estos mismos sitios, sale por diez u once. Es un aumento del 40-50%, más que la inflación general.
Hay dos razones. La primera es que el pescado de lonja ha subido por menor oferta (la pesca artesanal está en crisis, el número de barcas ha bajado). La segunda es que los costes operativos (aceite, luz, gas, personal) también han subido mucho.
Sigue siendo una comida económica comparada con la mayoría de alternativas. Pero ya no es "lo barato" que era. Es la comida tradicional que ha mantenido calidad al precio que la calidad cuesta.
## Lo que no me convence de Los Boliches hoy
No todo lo que pasa en Los Boliches me gusta. Voy a ser sincero sobre un par de cosas:
**La gentrificación.** En los últimos cinco años han abierto bares modernos con estética de Instagram que venden "tapas creativas" a precios altos. Son correctos pero no encajan con el barrio. No les deseo mal, pero no son lo que me trae a Los Boliches.
**La masificación estival.** De junio a septiembre, los sitios buenos están siempre llenos. Si no vas antes de las dos o después de las cuatro, no pillas mesa. Y la calidad baja porque la cocina no da abasto.
**Los sitios que dependen solo del turismo.** Hay establecimientos que abren en junio y cierran en septiembre, con plantilla de temporada y menús plastificados. No tienen ninguna relación con el barrio ni con el producto. Les evito.
## La ruta que hago cuando vienen de fuera
Cuando tengo a amigos o familia visitándome y quieren "comer pescaíto de verdad", la ruta es clara: llegamos a Los Boliches sobre las 13:30, tomamos una cerveza con aceitunas en El Caballo en la barra, a las 14:00 nos sentamos en La Freiduría del Puerto para la fritura variada, salimos sobre las 15:30 y cerramos con un helado paseando por el paseo marítimo hasta el puerto.
Es un plan barato (25 euros por persona todo incluido), corto (dos horas), y contundente. Nadie se queja.
## Para terminar
Los Boliches no es el sitio más de moda de la Costa del Sol ni el más instagrameable. Es un barrio marinero al que le queda poco de marinero original pero que todavía conserva sitios donde se come pescaíto frito como se ha comido siempre. Esos sitios no son muchos, pero están ahí. Y mientras estén, yo los voy a defender con mis diez euros un sábado al mediodía, porque cuando desaparezcan no volverán.
---
# ClickHouse desde cero (IV): materialized views, projections y TTL
URL: https://javiervalencia.net/post/clickhouse-desde-cero-iv-materialized-views-projections-y-ttl
*Cuarta entrega de la serie **[ClickHouse desde cero a pro](/search?tag=clickhouse-desde-cero)**. Tiempo de lectura estimado: 12 minutos.*
En la [entrega III](/post/clickhouse-desde-cero-iii-consultas-analiticas-en-profundidad) vimos cómo escribir consultas analíticas que en PostgreSQL serían ciencia ficción. En este post damos el siguiente salto: **cómo evitar recalcular lo mismo una y otra vez**.
Cuando un dashboard consulta los mismos datos cada minuto, con las mismas agregaciones, escanear mil millones de filas en cada petición es tirar recursos a la basura. ClickHouse ofrece tres herramientas complementarias: **materialized views**, **projections** y **TTL**.
## Materialized views: la joya de la corona
Un *materialized view* en ClickHouse **no es** lo que llamas así en PostgreSQL. No es una vista que refrescas manualmente. Es un **trigger de inserción**: se dispara cuando insertas datos en la tabla origen y escribe el resultado de una consulta en una tabla destino.
Conceptualmente: cada `INSERT` en la tabla base ejecuta una consulta sobre ese batch y escribe el resultado en otra tabla. Los datos quedan pre-agregados automáticamente, sin refresh, sin cron jobs.
### Ejemplo: estadísticas diarias de pageviews
Tabla base (recordando la [entrega I](/post/clickhouse-desde-cero-i-instalacion-y-primeros-pasos)):
```sql
CREATE TABLE blog.pageviews (
ts DateTime,
user_id UInt64,
path String,
country LowCardinality(String),
duration_ms UInt32
)
ENGINE = MergeTree()
ORDER BY (ts, user_id);
```
Tabla destino que guardará las pre-agregaciones:
```sql
CREATE TABLE blog.pageviews_daily (
day Date,
country LowCardinality(String),
views UInt64,
visitors AggregateFunction(uniq, UInt64),
duration AggregateFunction(avg, UInt32)
)
ENGINE = AggregatingMergeTree()
ORDER BY (day, country);
```
Presta atención a los tipos: `AggregateFunction(uniq, ...)` no guarda el resultado final de `uniq`, sino un **estado intermedio** que puede fusionarse con otros estados. Es lo que permite que la agregación siga siendo correcta cuando luego se insertan más datos el mismo día.
Y ahora, el materialized view que conecta ambas:
```sql
CREATE MATERIALIZED VIEW blog.pageviews_daily_mv
TO blog.pageviews_daily
AS
SELECT
toDate(ts) AS day,
country,
count() AS views,
uniqState(user_id) AS visitors,
avgState(duration_ms) AS duration
FROM blog.pageviews
GROUP BY day, country;
```
A partir de ese momento, cada `INSERT` en `pageviews` dispara el MV, que inserta (o actualiza) las filas correspondientes en `pageviews_daily`. No hay que hacer nada más.
### Consultando los estados
```sql
SELECT
day,
country,
sum(views) AS views,
uniqMerge(visitors) AS visitors,
avgMerge(duration) AS avg_ms
FROM blog.pageviews_daily
WHERE day >= today() - 30
GROUP BY day, country
ORDER BY day DESC;
```
Fíjate en los sufijos `*State` y `*Merge`. El MV escribe `uniqState`. La consulta lee con `uniqMerge`, que consolida los estados de cada partición de `pageviews_daily`. ClickHouse se encarga del resto.
Consultar 30 días desde `pageviews_daily` lee unas pocas miles de filas. Hacer la misma consulta contra `pageviews` podría leer cientos de millones. La diferencia es de dos o tres órdenes de magnitud.
### Backfill de datos históricos
Si creas el MV cuando la tabla base ya tiene datos, el MV **no se aplica retroactivamente**. Tienes que poblar la tabla destino manualmente:
```sql
INSERT INTO blog.pageviews_daily
SELECT
toDate(ts) AS day,
country,
count() AS views,
uniqState(user_id) AS visitors,
avgState(duration_ms) AS duration
FROM blog.pageviews
WHERE ts < today() -- histórico; el MV se encarga del resto
GROUP BY day, country;
```
Cuidado con solapar ventanas: suele ser más seguro parar los inserts en la base, crear el MV, hacer el backfill con `WHERE ts < X`, y reanudar.
### Patrones útiles
- **MV por granularidad**: un MV por hora, otro por día, otro por mes. Las consultas eligen el más fino que cumpla su rango.
- **Cadenas de MVs**: el destino de un MV puede ser origen de otro MV. Útil para segundo nivel de agregación.
- **MVs con joins**: posible, pero con asterisco. Solo se ejecutan sobre la tabla "izquierda" cuando hay inserts ahí. Cambios en la tabla joinada no los ven.
## Projections
Las *projections* son como materialized views encapsulados dentro de la propia tabla. ClickHouse las gestiona de forma transparente: cuando haces una consulta, el planner mira si alguna *projection* de la tabla responde mejor que la tabla principal.
Se declaran en el `CREATE TABLE`:
```sql
CREATE TABLE blog.pageviews (
ts DateTime,
user_id UInt64,
path String,
country LowCardinality(String),
duration_ms UInt32,
PROJECTION by_country (
SELECT
country,
toStartOfDay(ts) AS day,
count(),
uniq(user_id)
GROUP BY country, day
),
PROJECTION by_path (
SELECT path, count() GROUP BY path
)
)
ENGINE = MergeTree()
ORDER BY (ts, user_id);
```
O añadirlas a una tabla existente:
```sql
ALTER TABLE blog.pageviews
ADD PROJECTION by_country (
SELECT country, toStartOfDay(ts) AS day, count(), uniq(user_id)
GROUP BY country, day
);
ALTER TABLE blog.pageviews MATERIALIZE PROJECTION by_country;
```
Ventajas sobre los MVs:
- **Transparentes**: no cambias las consultas, el planner decide.
- **Siempre consistentes**: viven dentro de la misma tabla y son atómicas.
- **Rebuild automático**: `MATERIALIZE PROJECTION` reprocesa los *parts* existentes.
Desventajas:
- Requieren más disco (cada *projection* es una copia de los datos con otra `ORDER BY`).
- Las proyecciones normales (sin `GROUP BY`) son menos flexibles que un MV.
- No sirven para joins ni para tablas destino distintas.
Regla práctica: empieza con *projections* para casos simples y pasa a MVs cuando la lógica sea más compleja o cuando quieras controlar explícitamente dónde se almacenan los datos.
## TTL: ciclo de vida automático
En casi cualquier analytics acabas teniendo una política: "guarda 12 meses de detalle y 36 de agregado". El TTL de ClickHouse automatiza eso.
### TTL de filas: borrar automáticamente
```sql
CREATE TABLE blog.pageviews (
ts DateTime,
...
)
ENGINE = MergeTree()
ORDER BY (ts, user_id)
TTL ts + INTERVAL 12 MONTH;
```
Durante los merges, ClickHouse descarta las filas cuya `ts + 12 MONTH` ya ha pasado. No es instantáneo, pero tampoco corre prisa: lo hace en background.
Puedes forzarlo:
```sql
ALTER TABLE blog.pageviews MATERIALIZE TTL;
```
### TTL a otro disco (movimiento por edad)
Si tienes discos SSD rápidos y HDDs grandes, puedes mover los datos viejos a los lentos:
```sql
TTL ts + INTERVAL 3 MONTH TO DISK 'cold',
ts + INTERVAL 24 MONTH DELETE;
```
Necesitas configurar un *storage policy* con varios discos en el servidor. Es la receta para dejar en SSD los últimos 3 meses (los que reciben tráfico de dashboards) y en HDD el histórico.
### TTL de columna
Puedes vaciar columnas específicas después de cierto tiempo:
```sql
CREATE TABLE blog.pageviews (
ts DateTime,
user_id UInt64,
path String,
ip String TTL ts + INTERVAL 30 DAY,
user_agent String TTL ts + INTERVAL 90 DAY,
...
)
ENGINE = MergeTree()
ORDER BY (ts, user_id);
```
Útil para políticas de privacidad: la IP se vacía a los 30 días, el user agent a los 90, y las métricas agregadas se mantienen indefinidamente.
### TTL con GROUP BY (expiración agregando)
Una variante potente: en lugar de borrar, agrega y sigue guardando.
```sql
ENGINE = MergeTree()
ORDER BY (ts, country)
TTL ts + INTERVAL 6 MONTH
GROUP BY toStartOfDay(ts), country
SET views = sum(views);
```
A los 6 meses, en lugar de borrar las filas, colapsa cada día-país en una sola fila con las sumas. Ahorras espacio manteniendo agregado.
## Un diseño completo
Ejemplo para un producto con ~100M pageviews mensuales:
```sql
-- Tabla base con detalle, 12 meses
CREATE TABLE blog.pageviews (
ts DateTime,
user_id UInt64,
path String,
country LowCardinality(String),
device LowCardinality(String),
duration_ms UInt32
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (ts, user_id)
TTL ts + INTERVAL 12 MONTH;
-- Agregado diario, sin expirar
CREATE TABLE blog.pageviews_daily (
day Date,
country LowCardinality(String),
device LowCardinality(String),
views UInt64,
visitors AggregateFunction(uniq, UInt64)
)
ENGINE = AggregatingMergeTree()
ORDER BY (day, country, device);
CREATE MATERIALIZED VIEW blog.pageviews_daily_mv
TO blog.pageviews_daily
AS
SELECT
toDate(ts) AS day,
country,
device,
count() AS views,
uniqState(user_id) AS visitors
FROM blog.pageviews
GROUP BY day, country, device;
```
Los dashboards consultan `pageviews_daily` y son instantáneos. Las consultas ad hoc que necesitan detalle van a `pageviews`. El TTL limpia los datos crudos al año. El agregado se guarda para siempre: ocupa pocos KB por día.
Este patrón (tabla cruda + MV de agregado + TTL) es la receta estándar en ClickHouse. Si dominas los tres, ya estás resolviendo el 90% de los casos.
## Cosas a evitar
- **No hacer el backfill** al crear un MV sobre una tabla con datos históricos. El MV solo se aplica a inserts nuevos.
- **Hacer MVs sobre MVs sin cuidado**. Un error en el primero se propaga al segundo.
- **Olvidar que los MVs se ejecutan en el contexto del `INSERT`**. Si el MV falla, el insert falla (salvo que uses `materialized_views_ignore_errors`).
- **Abusar de projections en tablas con alta tasa de escritura**. Cada *projection* multiplica el trabajo de los merges.
- **TTL demasiado agresivo sin *storage policies*** en producción, sin tener un snapshot por si acaso. Borrar es fácil, reconstruir no.
## Por dónde seguir
- **[V: producción, replicación y clusters](/post/clickhouse-desde-cero-v-produccion-replicacion-y-clusters)** — lo que viene después: replicación, sharding, backups y operar el servicio bajo presión.
Si estás aterrizando en la serie:
- [I: instalación y primeros pasos](/post/clickhouse-desde-cero-i-instalacion-y-primeros-pasos)
- [II: tipos de datos y MergeTree](/post/clickhouse-desde-cero-ii-tipos-de-datos-y-mergetree)
- [III: consultas analíticas en profundidad](/post/clickhouse-desde-cero-iii-consultas-analiticas-en-profundidad)
---
# Clarkson's Farm y por qué los urbanitas deberíamos ver más campo
URL: https://javiervalencia.net/post/clarksons-farm-y-por-que-los-urbanitas-deberiamos-ver-mas-campo
Jeremy Clarkson compró una granja de 400 hectáreas en los Cotswolds, despidió al granjero que la llevaba y decidió hacerlo él mismo. Sin experiencia. Sin formación. Con toda la arrogancia del mundo y un tractor de un millón de libras que no cabe por ningún camino. De esa premisa absurda salió Clarkson's Farm, que empecé a ver por curiosidad y que se ha convertido en una de las series que más me han hecho pensar.
## No es lo que parece

Si solo conoces a Clarkson por Top Gear y The Grand Tour, esperas coches, explosiones y humor de señor británico de sesenta años. Y hay algo de eso, sobre todo al principio. Pero Clarkson's Farm es otra cosa. Es un programa sobre lo difícil que es producir comida, lo injusto que es el sistema que rodea a los agricultores y lo desconectados que estamos los que vivimos en ciudades de la realidad del campo.
Clarkson empieza siendo el payaso que no sabe nada y que hace todo mal. Pero temporada a temporada se transforma en alguien que genuinamente se preocupa por su granja, por sus animales, por sus empleados y por el sector agrario en general. No es un acto. Se le nota. Y esa transformación es lo que hace que el programa trascienda el entretenimiento.
## Kaleb y Gerald
Los mejores personajes no son Clarkson. Son Kaleb Cooper, el joven granjero local que le enseña todo, y Gerald, el encargado de los muros de piedra que lleva décadas trabajando en esas tierras y al que nadie entiende porque habla con un acento del campo inglés impenetrable.
Kaleb tiene veintipocos años y sabe más de agricultura que Clarkson sabrá jamás. Es la representación perfecta del conocimiento práctico frente al teórico. Clarkson llega con ideas sacadas de YouTube y Kaleb le dice que no, que así no funciona, y tiene razón siempre. Hay algo reconfortante en ver a un chaval joven ser el experto y al famoso millonario ser el aprendiz torpe.
Gerald es otra historia. Gerald es la conexión con una forma de vida que está desapareciendo. Sabe hacer muros de piedra seca como se hacían hace siglos. No usa tecnología. No necesita tecnología. Tiene un conocimiento transmitido de generación en generación que no está en ningún libro y que cuando él se retire no quedará nadie que lo tenga.
## Lo que no sabemos de nuestra comida

Lo que más me ha impactado de la serie es darme cuenta de lo poco que sé sobre la comida que como todos los días. No tenía ni idea de lo que cuesta producir un kilo de trigo. No sabía que un agricultor puede trabajar un año entero y perder dinero porque el precio que le pagan por sus productos no cubre los costes. No sabía que las regulaciones medioambientales, siendo necesarias, a veces son tan contradictorias que hacen imposible cumplirlas todas al mismo tiempo.
Clarkson se enfrenta a todo esto con cámaras delante y lo hace visible. Cuando el supermercado le ofrece treinta peniques por un litro de leche que le cuesta cuarenta producir, no es un dato en un periódico: es un señor cabreado en una reunión real con gente real que le dice que es lo que hay.
Vivo en España, no en Inglaterra, pero las dinámicas son las mismas. Los agricultores producen la comida que comemos todos los días y cobran menos de lo que cuesta producirla. Los márgenes se los quedan los intermediarios y las grandes superficies. Y nosotros compramos la lechuga a un euro sin plantearnos cómo es posible que algo que ha tardado semanas en crecer, que alguien ha sembrado, regado, cuidado y cosechado, cueste menos que un café.
## La burocracia como enemigo
Uno de los temas recurrentes de la serie es la burocracia. Clarkson quiere abrir una tienda en la granja para vender sus productos directamente. Le dicen que no. Quiere montar un restaurante. Le ponen trabas. Quiere hacer un lago. Necesita permisos que tardan meses. Quiere plantar un tipo de cultivo. Resulta que hay una regulación que lo prohíbe en su parcela concreta.
No es que las regulaciones sean malas en sí mismas. Muchas existen por buenas razones. Pero la serie muestra con claridad meridiana cómo el exceso de burocracia asfixia a la gente que intenta hacer cosas. Y cómo las normas que se escriben en despachos de la ciudad no siempre tienen sentido cuando las aplicas en un campo de Oxfordshire.
Cualquiera que haya montado un negocio en España se identifica con esto. La sensación de que el sistema no está diseñado para ayudarte sino para que demuestres que mereces que te dejen trabajar. Los formularios, los plazos, las ventanillas, los informes que nadie va a leer. Clarkson lo vive con cara de incredulidad y yo lo veo asintiendo desde el sofá.
## La conexión con la tierra

Hay un episodio en el que Clarkson asiste al parto de una de sus vacas y se emociona. El tipo que destruía caravanas con tanques en Top Gear se emociona viendo nacer un ternero. Y no es actuación. Se le quiebra la voz.
Eso dice algo importante sobre lo que nos pasa cuando nos desconectamos de la naturaleza. Vivimos en casas de hormigón, compramos comida envuelta en plástico, no sabemos de dónde viene el agua que sale del grifo y nos parece normal. Pero en algún lugar de nuestro cerebro hay un cable que conecta con la tierra, con los animales, con los ciclos de la naturaleza, y cuando ese cable hace contacto pasan cosas.
No digo que todos tengamos que comprar una granja. Digo que ver Clarkson's Farm me ha hecho más consciente de lo que como, de dónde viene y de lo que cuesta producirlo. Y que esa conciencia, por pequeña que sea, cambia cosas. Cambias lo que compras, dónde lo compras y lo que estás dispuesto a pagar por ello.
## Por qué la recomiendo
Recomiendo Clarkson's Farm a todo el mundo, no solo a los fans de Clarkson. A los que viven en ciudades y creen que la comida aparece mágicamente en los supermercados. A los que piensan que el campo es aburrido. A los que no entienden por qué los agricultores protestan. A los padres que quieren que sus hijos vean algo que no sean influencers haciendo el tonto.
Es divertida, es informativa, es emotiva y es importante. Y si después de verla sigues comprando la lechuga a un euro sin pensarlo, al menos lo harás sabiendo lo que hay detrás. Que ya es más de lo que sabías antes.
---
# Un mes publicando a diario: balance
URL: https://javiervalencia.net/post/un-mes-publicando-a-diario
Hoy hace un mes que empecé a publicar un post diario en este blog. Treinta días consecutivos, treinta piezas, treinta momentos en los que he tenido que decidir sobre qué escribir, cómo empezarlo y cuándo dar un texto por terminado. El experimento tiene una lógica sencilla: llevaba años diciéndome que quería escribir más y no lo hacía. Cuando algo no sale solo, la única forma que conozco de arrancarlo es convertirlo en obligación pública. Este post es el balance de ese mes.

## Qué esperaba y qué no
Empecé con dos miedos claros. El primero, quedarme sin ideas a la semana. El segundo, que la calidad cayera en picado a partir del día diez y tuviera que decidir entre bajar el estándar o abandonar el plan.
Ninguna de las dos cosas ha pasado, aunque por razones distintas a las que esperaba.
Las ideas no se han agotado porque pronto descubrí que el problema no era falta de temas sino exceso. Una vez abres la mano y te das permiso para escribir sobre cualquier cosa (técnica, personal, anécdotas, opiniones sueltas), te encuentras con una lista creciente de borradores, ideas a medias y fragmentos. Lo difícil no es tener sobre qué escribir: es decidir qué publicar hoy cuando tienes siete temas esperando.
La calidad, por otro lado, sí ha fluctuado. No de forma catastrófica, pero sí ha habido días en los que he publicado un texto que, con tres días más, habría sido bastante mejor. Con el tiempo he aprendido que eso está bien. Un blog personal no es una revista literaria. Publicar un texto correcto vale más que no publicar uno excelente.
## El tiempo real que cuesta
Antes del experimento, si alguien me hubiera preguntado cuánto tardo en escribir un post, habría dicho que unas dos horas. La realidad, con treinta posts de muestra, es otra: la media está cerca de las tres horas por post, si sumamos la idea inicial, el borrador, la edición y las revisiones finales.
Tres horas por día, treinta días, noventa horas al mes. Es un compromiso de tiempo enorme para un proyecto personal. No lo podía sostener indefinidamente y lo sabía desde el principio. Era un experimento con fecha de caducidad: un mes, y luego reevaluar.

Lo interesante es que no todas las tres horas son iguales. Las primeras dos son el trabajo duro: bajar la idea, estructurar, encontrar el ángulo. La tercera es pulido: releer, cortar, ajustar. Con más práctica, probablemente podría bajar la media a dos horas por post. Pero dos horas diarias en algo que no genera ingresos directos sigue siendo mucho.
## Qué he publicado
De los treinta posts, unos doce son técnicos (Go, DevOps, bases de datos, herramientas), diez son personales (reflexiones, rutinas, reseñas de películas y series) y ocho son híbridos (reflexiones sobre la vida adulta con un componente técnico o lifestyle local).
Los técnicos son los más fáciles de escribir porque son los más estructurados. Sé de qué hablo, hay una secuencia natural (problema, contexto, solución, ejemplos) y las objeciones las he oído mil veces. En tres horas sale un post técnico decente sin demasiada fricción.
Los personales son lentos. Lo que debería ser fácil ("escribir sobre lo que piensas") es en realidad lo más difícil, porque exige decidir qué cuentas y qué no, y cómo lo cuentas sin sonar presuntuoso ni plano. He reescrito posts personales tres o cuatro veces cuando la primera versión sonaba a postureo. Los técnicos rara vez necesitan tanto trabajo.
## Lo que ha pasado con los lectores
En cifras, el blog ha pasado de ser visitado por nadie a ser visitado por muy poca gente. Pero ha habido un patrón interesante: los posts que yo esperaba que funcionaran bien (los técnicos más profundos) han tenido lecturas menores que los posts personales, que pensaba que no interesarían a nadie.

El post sobre por qué sigo leyendo en papel en 2026 ha tenido cuatro veces más visitas que el post sobre generics en Go, que fue el más ambicioso técnicamente del mes. Esto no me sorprende del todo (los textos con ángulo personal suelen difundirse más) pero sí me ha hecho replantear qué tipo de blog quiero tener. Si solo persigo tráfico, la estrategia es clara. Pero creo que quiero un equilibrio, aunque el algoritmo premie un lado sobre otro.
## La disciplina fue más fácil de lo que pensaba
Hay una trampa común en los retos de "publicar todos los días durante X": los primeros tres días son fáciles, los siguientes diez son duros, del once al veinte es cuando la gente abandona, y si sobrevives al veinte, los últimos diez se vuelven fáciles de nuevo porque ya tienes momentum.
En mi caso el patrón fue similar pero menos doloroso de lo que anticipaba. La clave fue una decisión inicial: nunca escribir el post del día el mismo día. Siempre tenía un borrador, como mínimo, adelantado. Esto significa que nunca me he encontrado a las once de la noche sin saber qué publicar al día siguiente. El estrés del "no tengo nada" no ha existido porque el sistema estaba diseñado para evitarlo.
Cuando una máquina de escribir a diario se rompe, casi siempre es por falta de reservas. Si tienes tres borradores por delante, una mala tarde no te obliga a publicar basura; simplemente usas uno de los que tenías listos.
## Lo que no ha funcionado
Hubo dos cosas que intenté y que tuve que abandonar.
La primera fue un intento de planificar por categorías: lunes técnico, martes personal, etc. En teoría suena bien, en la práctica fue rígido. A veces la idea que tenía madurada era técnica en un día "personal" según el plan, y terminaba publicando algo forzado solo para cumplir la categoría. Al cabo de una semana lo dejé correr. Mejor publicar lo que está listo que lo que toca.
La segunda fue incluir fotografías propias. La idea era añadir una foto mía al principio de cada post. Duró cinco días. Hacer una foto decente añade veinte minutos que no tenía, y la mayoría de las veces el texto no necesitaba foto. Volví a un sistema más ligero: una imagen conceptual cuando el post lo pide, nada cuando no.
## Lo que sí ha funcionado
Lo que ha funcionado, y me ha sorprendido lo mucho que ha funcionado, es el efecto sobre el resto de mi día. Escribir un post decente requiere un tipo de concentración que es incompatible con estar disperso. Para escribir bien, tengo que haber pensado bien. Y pensar bien es un músculo que he ido reactivando con este mes.
He notado que los días que escribo son días con más energía, no menos. Es contraintuitivo: uno pensaría que después de tres horas de escritura estaría agotado. Y sí, lo estoy físicamente, pero mentalmente estoy más enfocado el resto del día. Es un tipo de ordenación mental que, ahora que lo tengo, no quiero perder.
También ha sido un ejercicio de autoconocimiento. Escribir obliga a articular. Muchas cosas que creía pensar con claridad resulta que las pensaba con mucha bruma. Sentarme a escribir sobre ellas me ha obligado a formularlas, y formular cosas cambia cómo las entiendes.
## ¿Lo vuelvo a hacer?
No un mes seguido. Pero sí un hábito regular, probablemente tres o cuatro posts por semana. La intensidad del mes diario era insostenible con mi vida (trabajo, familia, otros compromisos) pero el hábito más moderado es completamente realista.
El formato que voy a probar a partir de mañana es: cinco o seis posts a la semana, mezcla de técnicos y personales, sin días fijos pero con un mínimo semanal. Si un día no hay post, no pasa nada. El objetivo es mantener el músculo activo sin convertir la escritura en una obligación pesada.
## La conclusión
Un mes de publicación diaria en un blog personal no te va a cambiar la vida, pero sí te va a enseñar bastantes cosas sobre ti. Aprendes cuánto sabes sobre los temas que creías dominar. Aprendes cuánto te cuesta articular lo que piensas. Aprendes qué tipo de contenido te sale solo y cuál tienes que forzar. Aprendes que el miedo al folio en blanco se disuelve cuando no queda más remedio que llenarlo.
Lo que queda cuando pasa un mes así es algo difícil de cuantificar pero muy claro de sentir: la confianza de que eres capaz de producir, de que tienes cosas que decir, de que escribir no es algo reservado para profesionales. En el peor de los casos, has escrito treinta textos mediocres. En el mejor, has construido una prueba pública de que te dedicas a pensar en voz alta. A mí me ha merecido la pena. Y eso basta.
---
# Al campo con los colegas: el plan que nunca falla
URL: https://javiervalencia.net/post/al-campo-con-los-colegas
Hay planes con los colegas que funcionan a medias y planes que no fallan nunca. En la categoría de los que no fallan, para mí siempre ha estado el mismo: un domingo por la mañana, cuatro o cinco amigos, una ruta de campo que no sea extenuante, una pausa a mediodía con bocadillo y cerveza, y vuelta a casa a media tarde. Llevo haciéndolo desde que tenía veintipocos y no me he cansado todavía. Este post es mi intento de explicar por qué.

## Qué tiene de especial el campo
El campo, como escenario para un plan con amigos, tiene tres ventajas sobre cualquier alternativa urbana que conozco.
**La primera es que elimina distracciones.** En un bar hay otras mesas, conversaciones cruzadas, televisión, música. En una cena en casa, hay niños, cónyuges, llamadas de trabajo. En el campo no hay nada de esto. Estás tú, los amigos, el paisaje. El silencio que se produce cuando camináis en fila india durante diez minutos sin decir nada es parte del plan, no un problema.
**La segunda es que fuerza el movimiento.** Una caminata de dos o tres horas no es una hazaña deportiva, pero sí es movimiento sostenido. Y el movimiento, como cualquiera que haya caminado con alguien sabe, desbloquea conversaciones. Hay cosas que solo se cuentan cuando la mirada no está fija en quien te habla sino en el suelo delante de ti. El caminar es una excusa para hablar sin la presión del contacto visual constante.
**La tercera es que reduce el coste.** Un domingo de senderismo con bocadillo y cerveza sale por menos de diez euros por persona. No hay reservas, no hay tiempos establecidos, no hay menú que elegir. Es un plan casi libre de logística y casi libre de presupuesto.
## La ruta que hacemos con más frecuencia
A quince minutos de Mijas Costa, subiendo por la carretera de Mijas pueblo, hay una zona de bosque mediterráneo con varias rutas marcadas. La que más frecuentamos es la del pinar de Mijas hacia la ermita del Calvario. Son unos cuatro kilómetros de ida, con un desnivel moderado, y se hace en una hora y cuarto a ritmo de conversación.
El terreno es fácil: pista forestal ancha, sombra abundante de pinos carrascos, algunos tramos con vistas al mar. No hay pasos técnicos ni riesgo de perderse. Es la ruta perfecta para un grupo mixto donde alguno está en forma y otros vienen directos del sofá.
En primavera la ruta está llena de flores silvestres (jaras, romeros, torvisco) y se oyen pájaros que en la costa no escuchas. En otoño las piñas caen y el suelo cruje al andar. En invierno hay días en que arriba ves nieve en los picos de la sierra de Mijas. En verano mejor evitar, hace demasiado calor a partir de las once.

## El momento del bocadillo
A mitad de ruta hay una zona con sombra y piedras grandes donde paramos a comer. Esta es la parte del plan que más he aprendido a valorar con los años.
Cada uno llevamos nuestro propio bocadillo, generalmente algo que hemos hecho esa mañana en casa. Jamón con tomate, lomo con queso, atún con pimientos, tortilla fría. Nada sofisticado. La combinación que me gusta repetir es pan rústico con lomo de la sierra y tomate rallado con aceite. Pocas cosas saben mejor a media mañana después de haber andado una hora en el monte.
La cerveza la cargamos en una mochila con hielo. Pueden ser cuatro o cinco personas con un par de cervezas cada uno. Nada de botellas grandes, nada de alcohol fuerte; una cerveza después de un paseo, con el sudor que se va evaporando al sentarse, es otra dimensión de placer.
La pausa dura entre media hora y una hora. Hablamos, miramos al horizonte, alguien se echa unos minutos al sol. Los móviles están guardados por costumbre (no por norma explícita: por costumbre). Es el tramo del plan donde pasan las cosas importantes.
## Las conversaciones del campo
He notado que las conversaciones que ocurren en el campo tienen una cualidad distinta de las que ocurren en un bar. Son más reposadas. Se cuentan cosas que en otros contextos no se contarían: dudas profesionales, preocupaciones sobre padres mayores, inquietudes sobre los hijos, proyectos que ni siquiera están formulados todavía.
Creo que hay dos razones. La primera es que el entorno físico comunica calma: las pantallas, el ruido de la ciudad, la prisa no están. Cuando el entorno te dice "aquí no hay urgencia", tu cerebro se relaja. La segunda es que la caminata ha ido soltando tensiones físicas durante la hora previa. Llegas al momento del bocadillo con menos corazas puestas.
El resultado es que los amigos con los que voy al campo con regularidad son los que más sé cómo están en cualquier momento dado. No tengo que preguntar. Las cosas se cuentan solas.
## Por qué domingos y no otro día
Los domingos tienen una ventaja específica para este plan: son el día que menos se llena de compromisos. Los sábados tiene cualquiera una boda, un cumpleaños, una comida familiar. Los domingos por la mañana son tierra de nadie para casi todo el mundo. Si reservas ese hueco para un plan con amigos, raramente tienes que cancelarlo por un compromiso mayor.
Además, los domingos ofrecen el balance perfecto: has descansado bastante el sábado, pero aún te queda medio domingo para la familia (los que la tienen en casa) o para uno mismo (los que no). Un plan de mañana de diez a dos deja la tarde entera libre.
## La regla del grupo estable
He probado distintas configuraciones de grupos en el monte y una cosa que he aprendido es que el grupo funciona mejor cuando es casi siempre el mismo.
Con amigos que ya conoces, el ritmo se estabiliza (todos sabéis quién anda rápido y quién lento), los silencios son cómodos, las bromas repetidas funcionan, no hay que explicar cosas de fondo. Con grupos cambiantes siempre hay una ceremonia inicial de situarse que consume energía. Con los mismos, el plan se pone en piloto automático.

Eso sí: de vez en cuando incluir a alguien nuevo tiene su gracia, especialmente si es alguien de fuera del circuito habitual. Da una perspectiva distinta. Pero no como norma. La norma es el grupo estable.
## Lo que no funciona
Algunos errores que he cometido y recomiendo evitar:
**Elegir rutas demasiado exigentes.** El objetivo no es el entrenamiento ni la proeza, es la conversación. Una ruta de tres picos con doce kilómetros te deja sin aliento para hablar. Mejor una ruta modesta que termines con energía.
**Llevar demasiada comida.** El bocadillo es el bocadillo. Cuando alguien lleva también embutidos surtidos, croquetas fritas, empanada, la pausa se convierte en un picnic elaborado y pierdes la ligereza. Come sobrio, cena luego en casa.
**Programar horarios estrictos.** El plan del domingo no funciona con "nos vemos a las 9:15 y estamos de vuelta a las 13:00". Funciona con "nos vemos hacia las diez y hacia las dos ya estaremos de vuelta". La flexibilidad temporal es parte del disfrute.
**Llevar invitados sorpresa.** Si alguien quiere traer a un amigo, que avise. Grupos nuevos que aparecen sin aviso rompen la dinámica establecida.
## Lo que gano cada domingo
Después de una mañana de monte con los amigos, hay efectos concretos que noto:
- Duermo mejor esa noche.
- Empiezo la semana con la sensación de haber hecho algo.
- He pasado cuatro horas sin pantallas, algo que raramente consigo en días laborables.
- Tengo al día la información sobre cómo están las vidas de mis amigos.
- He respirado aire limpio y he visto algo más que asfalto y pantallas.
Ninguno de estos efectos es dramático por sí solo. La suma, cada domingo, es la diferencia entre una vida densa y una vida espaciosa.
## Para cerrar
Si llevas tiempo sin salir al campo con amigos, prueba a organizar un domingo. No hace falta grandes rutas ni equipo caro. Una zona verde cerca, cuatro amigos, un bocadillo por cabeza, una cerveza al llegar. En tres horas tienes hecho el mejor plan de tu fin de semana.
Las amistades largas se sostienen con planes como este. No con cenas de cumpleaños ni con WhatsApps: con horas compartidas en movimiento, con silencios compartidos en paisajes buenos, con bocadillos comidos en la misma piedra. El campo, a diferencia de casi cualquier otro plan, te da todo eso a la vez. Por eso no falla.
---
# Tapeo después de los cuarenta
URL: https://javiervalencia.net/post/tapeo-despues-de-los-cuarenta
A los veintitantos, el tapeo era un maratón. Salías el sábado a las dos de la tarde, parabas en el primer bar para una caña y un montadito, te ibas al segundo para otra caña y dos tapas, al tercero para una tapa grande y una tónica, y así hasta las ocho de la noche encadenando sitios, cervezas y calorías con una alegría que no me explico cómo sobrevivíamos. Luego cenábamos. Luego salíamos. Volvíamos a casa de madrugada y al día siguiente, a las doce, ya estábamos otra vez en el bar.
A los cuarenta y tantos, el tapeo es otra cosa. Y no es peor: es distinto. En algunos aspectos es mejor. En este post intento articular cómo ha cambiado mi relación con uno de los mejores planes españoles.
## Lo que ya no hago

El tapeo de los veinte era más cantidad que calidad. Se premiaba la resistencia sobre el gusto. El bar se elegía más por su precio y por su ambiente que por lo que servía. Y honestamente, con la boca joven, todo sabía más o menos bien: la adrenalina, las cervezas, la compañía, el calor de agosto en Madrid o en Granada tapaban los matices que no había.
A los cuarenta ya no hago rondas largas. No es cansancio físico exactamente, aunque un poco: es que a las cuatro tapas ya no disfruto. La quinta no añade. La sexta es penitencia. El cuerpo empieza a pedir pararse, y cuando no le haces caso te sale carísima la factura al día siguiente.
Tampoco bebo como antes. Una caña, dos cañas, y si la cosa va larga una tónica. La borrachera alegre de los veintitantos no compensa el precio físico y mental que pagas pasados los cuarenta. He aprendido a disfrutar del sabor del alcohol sin necesidad de sentirme pasado. Es una victoria que no sabía que iba a agradecer.
## Lo que sí hago ahora
El tapeo de los cuarenta es más curado, más pequeño, más lento. Dos o tres paradas en vez de seis. Cada parada más larga. Conversación más tranquila. Platos elegidos con más criterio.
He descubierto que **tres horas en un solo bar bueno** es mejor plan que cinco horas recorriendo seis bares mediocres. La economía del placer cambia: ya no me da subidón ir a un sitio nuevo cada veinte minutos. Me da más disfrute quedarme en un sitio bueno, pedir tres cosas bien hechas, y dejar que la conversación vaya.
También he aprendido a pedir mejor. A los veinte pedía por cantidad, por probar todo lo posible. Ahora pido por producto: qué hay de temporada, qué lleva aquí más tiempo, qué le sale mejor a este cocinero. Una ración bien elegida vale por cinco pedidas a ciegas.
## La hora cambia

A los veintitantos salíamos a tapear a las dos o las tres de la tarde y rodábamos hasta las ocho o nueve. Ahora empiezo a las dos y media o tres, como el plato fuerte en algún sitio a las cuatro, y a las siete estoy en casa con mis hijas. Lo he cambiado por una cosa importante: tapear se ha convertido en un plan compatible con mi vida familiar, no un sacrificio que la vida familiar tiene que absorber el domingo.
Cuando empiezas temprano y terminas temprano, el tapeo cabe en el fin de semana sin dinamitarlo. Es una pequeña reorganización horaria que me ha devuelto muchas tardes.
## El plato estrella ya no es siempre el mismo
A los veinte, mi menú universal era patatas bravas, calamares a la romana, croquetas y cerveza. Es la dieta española estándar del que todavía no se ha detenido a pensar qué le gusta de verdad.
A los cuarenta mi paleta ha cambiado bastante. Cosas que antes me parecían "raras" ahora me gustan enormemente: boquerones en vinagre con ajo fresco, berenjenas fritas con miel de caña, anchoas del Cantábrico de buena calidad, gambas a la plancha con solo sal. Cosas sencillas donde el producto es el noventa por ciento y la cocina el diez. Eran platos que no entendía a los veinte porque no tenía los matices del paladar para apreciarlos.
La fritura que tanto me gustaba sigue gustándome, pero la tolero peor. Las croquetas ahora las pido en sitios donde sé que las hacen ellos; las industriales las distingo al primer bocado y me parecen tristes. Las patatas bravas solo en sitios donde la salsa es casera, si no pido otra cosa.
Es una sofisticación gradual, no una postureo. Es el resultado de comer durante veinte años más y darte cuenta de que el disfrute aumenta cuando afinas.
## La compañía pesa más que nunca

A los veinte, el tapeo era casi un deporte colectivo. Íbamos en grupos de siete, ocho, diez personas. Dentro del grupo había subgrupos, conversaciones simultáneas, gente que venía y se iba. Era más fiesta que mesa.
A los cuarenta, con quién voy me importa más que adónde voy. Tres o cuatro personas, máximo. Todos amigos cercanos. Una conversación que no se fragmenta. Un bar con mesa sentada en vez de barra de pie. La calidad de la compañía es ahora más determinante para que el plan sea bueno que la calidad de las tapas.
Esto cambia quién llama a quién. A los veinte salías porque había ambiente en una plaza; te encontrabas con la gente allí. A los cuarenta organizas específicamente con dos personas con las que quieres estar. Es menos espontáneo pero mucho más satisfactorio.
## Lo que el tapeo de los cuarenta enseña
He pensado mucho por qué el tapeo sigue siendo un plan que defiendo con tanto entusiasmo, incluso con todas las diferencias respecto a cuando era joven. Y creo que es porque el formato en sí tiene virtudes que otros planes sociales no tienen.
**Permite la conversación larga** sin la formalidad de una cena. Puedes estar tres horas en un bar picoteando sin tener que estructurar el tiempo como una comida formal. Pides cuando te apetece, paras cuando quieres, sigues si te viene bien.
**No requiere agenda**. A diferencia de una cena en un restaurante con reserva, el tapeo es más informal. Si llegas tarde no pasa nada. Si se une alguien a mitad, se une. Si alguien tiene que irse antes, se va. Es social pero con menos fricción.
**Escala en intensidad**. Puedes hacer un tapeo ligero (una cerveza, tres tapas pequeñas) o uno más serio (cuatro raciones, postre, café). El mismo formato admite varios niveles de compromiso según el día.
**Es económico bien hecho**. Una tarde de tapeo en dos bares para tres personas sale por unos veinte-veinticinco euros por cabeza. Es más barato que una cena en un restaurante y más social que un café. La relación disfrute/gasto es excelente.
## Lo que no me funcionaría ahora
Hay versiones del tapeo que, honestamente, ya no puedo con ellas:
**Los tapeos de empresa**. La combinación de compromiso social forzado, jerarquía implícita y la presión de no quedar mal con nadie convierte un formato relajado en un campo de minas. Prefiero una cena formal de empresa con todo tasado que un tapeo en el que tengo que fingir espontaneidad durante tres horas.
**Los bares con televisión encendida**. Si voy a tapear, quiero conversación. Una televisión atrae miradas, interrumpe, cambia el ritmo de la mesa. Los bares que no tienen tele son raros pero los busco.
**El tapeo turístico**. Sitios del centro, carta plastificada, camarero con prisa, tapas genéricas. Es la caricatura del tapeo, no el plan real. Funciona con gente que nunca lo ha probado de verdad; para los que sí, es decepcionante.
## La conclusión que no te pedí
Después de veinte años tapeando, lo que más valoro es que el tapeo es un formato social español que todavía no ha sido colonizado por la cultura de la productividad ni por la prisa de los cuarenta. Cuando te sientas en un bar con amigos, la convención es que el tiempo se detiene. No hay cronómetro, no hay agenda, no hay prisa. Es un espacio protegido.
En la vida adulta, con calendario saturado y exigencias por todos los lados, tener un plan que te obliga a parar, a mirar a los ojos, a comer despacio y a dejar que la conversación fluya es casi un bien preciado. Los cuarenta le han dado al tapeo una función que a los veinte no tenía: la de antídoto. Y creo que por eso, más que por ninguna otra razón, voy a seguir tapeando cada sábado que pueda hasta que el cuerpo aguante.
---
# Desayuno molinero en Mijas: tradición que sobrevive
URL: https://javiervalencia.net/post/desayuno-molinero-en-mijas
Hay una parte de la Costa del Sol que resiste. No resiste de manera consciente, como si alguien estuviera en un comité diciendo "no os rindáis, que no se pierda"; resiste porque la gente sigue haciendo lo que hacía antes, por costumbre. Una de las formas en que se manifiesta esa resistencia es en el desayuno molinero: el desayuno tradicional campesino de la zona, compuesto por pan rústico, aceite de oliva virgen extra, sal, y a veces tomate rallado o ajo. Nada más. Y a veces, un café de puchero o un cortado.
En 2026, con el brunch instalado en cada esquina, pedir un desayuno molinero se ha vuelto casi un gesto político. Este post es sobre por qué sigue siendo, para mí, el mejor desayuno que se puede tener un sábado por la mañana en los alrededores de Mijas.

## Qué es exactamente
El nombre viene del mundo del campo. Los molineros (y los jornaleros, agricultores y gente que empezaba el día antes del amanecer) desayunaban esto: una rebanada gruesa de pan de pueblo, chorreada con aceite de oliva del año y sal. El pan era tostado o simplemente pan del día anterior; el aceite, del cortijo o del molino más cercano. Era calórico, sencillo y barato.
En su versión "urbana", que es la que encuentras en los bares de Mijas y alrededores, suele venir acompañado de tomate rallado con ajo, o sencillamente con sal gorda encima. Algunos sitios añaden una loncha de jamón serrano o un poco de queso curado, pero los puristas (yo entre ellos) dejan el plato limpio: pan, aceite, sal.
El café viene aparte. Si el sitio es serio, el café es de puchero o, al menos, es un café bien hecho con leche del tiempo. Los botes de leche fría y las cafeteras industriales que chorrean espuma azul no pintan nada en este desayuno.
## Dónde lo tomo yo
Hay tres sitios en la zona donde el desayuno molinero me parece auténtico. Ninguno está en el paseo marítimo ni tiene vistas al mar.
**Bar La Parada**, en el camino de entrada a Mijas pueblo, justo antes del aparcamiento grande. Es un bar pequeño, sin decoración, con seis mesas dentro y cuatro fuera. El pan lo traen del horno de leña de Alhaurín el Grande. El aceite es de la cooperativa de Benamargosa, aceite del año, con ese amargor elegante que solo tienen los aceites de verdad. La tostada con tomate cuesta 2,50 euros. El café solo con leche, 1,50. Tres euros con cincuenta por un desayuno completo es casi una broma en 2026.

**Venta El Romero**, un poco más arriba, en la carretera a Entrerríos. Sirven un desayuno molinero con jamón serrano ibérico cortado a cuchillo que es otra liga. Salen unos cinco euros, pero la calidad del jamón y del pan merece la diferencia.
**Chiringuito El Viejo Molino**, a pesar del nombre, no es un chiringuito costero sino un bar de carretera cerca de la venta de Alhaurín. Aquí el desayuno viene con una copa pequeña de moscatel opcional. Es excesivo para un sábado cualquiera pero una vez al año, en una mañana fría de invierno, es una cosa espectacular.
## Por qué sobrevive
En una zona colonizada por franquicias, por cafés con almendras picadas, por brunches de veinticinco euros con aguacate y huevo pochado, el desayuno molinero sobrevive porque tiene algo que el brunch nunca va a tener: **es local**.
El pan viene del horno de al lado. El aceite viene del molino de al lado. El tomate viene de la huerta de al lado. Si cierras los ojos y piensas en los ingredientes, todos están dentro de un radio de cincuenta kilómetros. El brunch es global: huevos holandeses, aguacate mexicano, salmón noruego. El desayuno molinero es lo que sale del suelo a media hora de coche.
Esto importa en dos sentidos. El primero es gastronómico: el producto fresco, local, de temporada, casi siempre es mejor que el industrial importado. Un aceite de un molino cercano que conoces tiene matices que un aceite envasado en una fábrica grande nunca va a tener. El segundo es económico: cada euro que dejas en el bar de al lado se queda en el circuito local, llega al panadero, al agricultor, al tostadero. Es una economía pequeña pero es real.
## La ceremonia del pan
Hay un detalle del desayuno molinero que me fascina: es un desayuno que exige una cierta técnica de consumo. No es automático.
Primero, tienes que echar la sal con cuidado. Demasiada sal mata el sabor del aceite; poca sal deja plano el pan. La proporción la descubres después de varias tostadas.
Segundo, el aceite se vierte al último momento, no antes. Si lo pones demasiado pronto, el pan lo absorbe y pierde la textura crujiente. Si lo pones demasiado tarde, la tostada se enfría. Hay una ventana de unos treinta segundos donde todo está en su punto.
Tercero, el tomate rallado, si hay, va en capas: primero el aceite, luego el tomate, luego otra pizca de sal. No mezclar antes. El contraste de texturas (pan crujiente, aceite untuoso, tomate jugoso, sal crujiente) es parte del plato.

Estos detalles parecen triviales pero determinan la diferencia entre un desayuno correcto y uno memorable. En los bares buenos, los camareros te dejan mover las piezas a tu ritmo. En los malos, te lo traen todo mezclado y emplatado "bonito", lo cual es peor.
## Lo que no recomiendo
No recomiendo comer un desayuno molinero en:
**Los hoteles grandes de la Costa del Sol.** El buffet internacional no tiene esto. Y si lo tiene, son versiones empobrecidas con pan industrial y aceite de marca blanca. Es casi peor una imitación mala que no tenerlo.
**Los sitios que lo llaman "desayuno andaluz tradicional" en inglés en la carta.** Si está traducido al inglés como plato conceptual, está pensado para el turista que viene a "probar lo local". No es un desayuno de verdad, es una experiencia.
**Los bares con café de máquina de cápsulas.** Un desayuno molinero con Nespresso es un acto de violencia cultural. El café o es decente o estropea toda la ceremonia.
## El lugar que ocupa en mi semana
Los sábados los reservo, casi siempre, para ir a desayunar fuera. No todos, pero dos o tres al mes. Es un plan barato (cuatro o cinco euros), corto (menos de una hora), y restaurador. Sales de casa a las nueve, estás de vuelta a las diez y media, y el día empieza con buen sabor literal y figurado.
Cuando tengo a mis hijas mayores de visita, lo hacemos juntos. Les enseño lo mismo que me enseñaron a mí: pan, aceite, sal, nada más. Ellas, que viven en entornos donde el brunch es la norma, al principio se sorprenden de lo simple que es. Después no paran.
## Por qué lo defiendo
Defiendo el desayuno molinero no porque sea el mejor desayuno posible en cualquier métrica absoluta. Hay otros desayunos más sofisticados, más variados, más adaptados a distintos gustos. Lo defiendo porque es un testimonio pequeño pero real de una forma de vida que está desapareciendo.
Cuando entras en un bar de carretera y pides un molinero, estás participando en una tradición que lleva generaciones. Estás comiendo lo mismo que comían tus abuelos cuando eran niños. Estás sosteniendo con tus cinco euros a un bar pequeño que no aparece en Google Maps de forma destacada. Estás diciendo, implícitamente, que prefieres esto a lo uniforme.
Y hay algo más: el desayuno molinero es honesto. No pretende ser nada. No viene con filtro de Instagram. No tiene una historia de marketing detrás. Es pan con aceite. Y sin embargo, si están bien los ingredientes, es uno de los mejores platos que hay.
## Para terminar
Si pasas por la Costa del Sol y tienes un sábado por la mañana libre, sáltate el hotel y métete en un bar cualquiera de pueblo. Pide un molinero con café. Gasta cuatro euros. Siéntate sin mirar el móvil. Come despacio.
Es el desayuno de la Andalucía real. Y en 2026, a pesar del ruido, sigue ahí, esperándote. Solo hay que saber dónde buscarlo.
---
# ClickHouse desde cero (III): consultas analíticas en profundidad
URL: https://javiervalencia.net/post/clickhouse-desde-cero-iii-consultas-analiticas-en-profundidad
*Tercera entrega de la serie **[ClickHouse desde cero a pro](/search?tag=clickhouse-desde-cero)**. Tiempo de lectura estimado: 12 minutos.*
Ya tienes ClickHouse funcionando (entrega [I](/post/clickhouse-desde-cero-i-instalacion-y-primeros-pasos)) y sabes diseñar tablas con tipos adecuados y `MergeTree` bien pensado (entrega [II](/post/clickhouse-desde-cero-ii-tipos-de-datos-y-mergetree)). Ahora toca la parte divertida: escribir consultas que, con PostgreSQL, no te habrías planteado ni intentar.
Este post no pretende listar todas las funciones (hay cientos), sino mostrar las familias que más usarás y las que te van a sorprender si vienes del SQL tradicional. Si aún no tienes claro por qué ClickHouse puede ser necesario en tu stack, léete [ClickHouse para desarrolladores que vienen de PostgreSQL](/post/clickhouse-para-desarrolladores-que-vienen-de-postgresql).
## Funciones de fecha y tiempo
Olvida `date_trunc` y `extract`. ClickHouse tiene una familia de funciones `toStart*` y `toXxxOf*` muchísimo más granular:
```sql
toStartOfMinute(ts)
toStartOfFiveMinutes(ts)
toStartOfFifteenMinutes(ts)
toStartOfHour(ts)
toStartOfDay(ts)
toStartOfWeek(ts) -- lunes
toStartOfMonth(ts)
toStartOfQuarter(ts)
toStartOfYear(ts)
toYear(ts), toMonth(ts), toDayOfMonth(ts), toDayOfWeek(ts)
toHour(ts), toMinute(ts), toSecond(ts)
```
Son funciones puras, muy baratas, y ClickHouse las conoce lo bastante bien como para aprovechar la clave de ordenación cuando filtras por ellas:
```sql
SELECT
toStartOfHour(ts) AS hour,
count() AS pv,
uniq(user_id) AS uniques
FROM pageviews
WHERE ts >= today() - INTERVAL 7 DAY
GROUP BY hour
ORDER BY hour;
```
Intervalos: `INTERVAL N SECOND/MINUTE/HOUR/DAY/WEEK/MONTH/QUARTER/YEAR`.
## Funciones de agregación
Además de las clásicas (`count`, `sum`, `avg`, `min`, `max`), ClickHouse añade una batería impresionante:
### Conteo distinto: la familia uniq
```sql
uniqExact(x) -- exacto, usa memoria proporcional a cardinalidades
uniq(x) -- HyperLogLog adaptativo, muy rápido, error ~0.5%
uniqHLL12(x) -- HLL con 12 bits de precisión
uniqCombined(x) -- híbrido, bueno para ~1M valores distintos
```
Para dashboards y métricas, `uniq` es suficiente y mucho más rápido que `uniqExact`. Para conteos de facturación, usa `uniqExact`.
### Cuantiles
```sql
quantile(0.95)(latency_ms)
quantiles(0.5, 0.9, 0.95, 0.99)(latency_ms) -- devuelve tuple
quantileTDigest(0.99)(latency_ms) -- más memoria, más preciso
quantileTiming(0.99)(latency_ms) -- muy rápido para valores en ms
quantileExact(0.5)(latency_ms) -- exacto, pero carga todo en memoria
```
Calcular p95 y p99 en ClickHouse es una línea. En PostgreSQL, ya sabes.
### Funciones de "primer/último"
```sql
any(x) -- un valor cualquiera del grupo (no determinista)
anyLast(x) -- el último insertado
argMin(x, by) -- el x cuyo by es mínimo
argMax(x, by) -- el x cuyo by es máximo
```
`argMax` es oro puro. Ejemplo: último estado conocido por usuario:
```sql
SELECT
user_id,
argMax(status, ts) AS last_status,
max(ts) AS last_seen
FROM events
GROUP BY user_id;
```
### Combinators
Aquí viene lo que no tiene PostgreSQL. Puedes sufijar cualquier función de agregación para modificar su comportamiento:
```sql
countIf(status = 'error') -- contar condicional, sin CASE
sumIf(cost, country = 'ES')
avgIf(duration, disposition = 'ANSWERED')
uniqArray(tags) -- unique sobre arrays aplanados
quantileMerge(quantile_state) -- consolidar estados intermedios
groupArray(x) -- agrega valores en un array
groupUniqArray(x) -- array de valores únicos
topK(10)(path) -- los 10 más frecuentes, aproximado
```
Con `-If` te ahorras el `CASE WHEN` en todas partes. Es idiomático:
```sql
SELECT
toStartOfHour(ts) AS hour,
countIf(disposition = 'ANSWERED') AS answered,
countIf(disposition = 'BUSY') AS busy,
countIf(disposition = 'NO ANSWER') AS no_answer,
avgIf(duration_ms, disposition = 'ANSWERED') AS avg_answered_ms
FROM cdrs
WHERE ts >= today() - INTERVAL 1 DAY
GROUP BY hour
ORDER BY hour;
```
Una sola pasada por los datos, múltiples agregaciones condicionales. El planner no tiene que hacer malabares.
## Arrays: ciudadanos de primera
Los arrays en ClickHouse son idiomáticos, no un añadido. Hay docenas de funciones que operan sobre ellos:
```sql
[1, 2, 3, 4] -- literal
arrayMap(x -> x * 2, [1,2,3]) -- lambda, devuelve [2,4,6]
arrayFilter(x -> x > 10, arr)
arraySum(arr), arrayAvg(arr)
arrayJoin(arr) -- "unnest": una fila por elemento
arrayEnumerate(arr) -- [1, 2, 3, ...]
arrayDistinct(arr)
arrayIntersect(a, b), arrayDifference(a, b)
has(arr, 'valor'), hasAny(arr, ['a','b']), hasAll(arr, ['a','b'])
length(arr), empty(arr)
```
### arrayJoin, el unnest explosivo
```sql
SELECT
arrayJoin(tags) AS tag,
count() AS posts
FROM posts
GROUP BY tag
ORDER BY posts DESC;
```
`arrayJoin` multiplica la fila por cada elemento del array. Si un post tiene 5 tags, genera 5 filas. Estándar para explotar datos semiestructurados.
### ARRAY JOIN
Variante de `JOIN` que hace lo mismo que `arrayJoin` pero permite mantener columnas del array original alineadas:
```sql
SELECT path, code, codes
FROM (
SELECT path, [200, 404, 500] AS codes
FROM urls
)
ARRAY JOIN codes AS code;
```
### Lambdas y funciones de orden superior
```sql
-- Posts con al menos un tag que empieza por 'data'
SELECT title
FROM posts
WHERE arrayExists(t -> startsWith(t, 'data'), tags);
-- Suma de valores positivos de un array
SELECT arraySum(x -> if(x > 0, x, 0), values) FROM metrics;
```
## Window functions
Disponibles desde ClickHouse 21.8 y estables desde 22.3. Sintaxis estándar:
```sql
SELECT
customer_id,
ts,
cost_eur,
sum(cost_eur) OVER (
PARTITION BY customer_id
ORDER BY ts
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total,
row_number() OVER (PARTITION BY customer_id ORDER BY ts) AS rn,
lag(cost_eur, 1) OVER (PARTITION BY customer_id ORDER BY ts) AS prev_cost
FROM cdrs
WHERE toYear(ts) = 2026;
```
Las funciones de ventana son más lentas en ClickHouse que las agregaciones puras. Úsalas cuando realmente las necesites. Para muchos casos, una combinación de `groupArray` + lambda es más eficiente.
## Joins: útiles, pero con cautela
ClickHouse soporta `INNER`, `LEFT`, `RIGHT`, `FULL`, `CROSS`, `ASOF` y `ANY` joins. La gran diferencia con PostgreSQL:
- Los joins se ejecutan en memoria.
- La **tabla de la derecha es la que se carga entera** (hash join). Pon siempre la tabla pequeña a la derecha.
- Para datasets enormes, ClickHouse ofrece `partial_merge` y `full_sorting_merge` como estrategias alternativas, pero suelen ser más lentas.
```sql
SELECT
o.customer_id,
c.name,
sum(o.total) AS total
FROM orders AS o
INNER JOIN customers AS c ON o.customer_id = c.id
GROUP BY o.customer_id, c.name;
```
Para tablas de lookup pequeñas (países, categorías, tarifas), lo idiomático es un **diccionario** (`Dictionary`), no un join. Los diccionarios se cargan en memoria una sola vez y permiten hacer lookups con funciones como `dictGet('tariffs', 'rate', customer_id)`. Es muchísimo más rápido.
### ASOF JOIN
Joina por aproximación temporal. Útil para correlacionar eventos con el estado vigente en un momento dado:
```sql
SELECT
q.ts,
q.symbol,
q.price,
t.trade_price
FROM quotes AS q
ASOF LEFT JOIN trades AS t
ON q.symbol = t.symbol AND q.ts >= t.ts;
```
Para cada fila de `quotes`, busca el último `trades` con `ts` ≤ al de la quote. Esto en PostgreSQL es un `LATERAL JOIN` horrible.
## Sampling
ClickHouse permite muestreo **determinista** si lo declaras al crear la tabla:
```sql
ENGINE = MergeTree()
ORDER BY (cityHash64(user_id), ts)
SAMPLE BY cityHash64(user_id);
```
Luego en consultas:
```sql
SELECT count()
FROM events
SAMPLE 0.1 -- 10% de los datos
WHERE ts >= today() - 7;
```
El muestreo es coherente: la misma consulta devuelve siempre el mismo subconjunto. Para métricas aproximadas sobre datos masivos, puede reducir tiempos de consulta 10×.
## WITH FILL: rellenar huecos
Una joya que no vas a volver a querer perder:
```sql
SELECT
toStartOfHour(ts) AS hour,
count() AS pv
FROM pageviews
WHERE ts >= today() - 1
GROUP BY hour
ORDER BY hour WITH FILL STEP INTERVAL 1 HOUR;
```
Rellena automáticamente las horas sin datos con `0`. Esencial para dashboards: sin esto, las series temporales tienen huecos visuales cuando hay inactividad.
## EXPLAIN y análisis de consultas
Antes de optimizar, mide. ClickHouse tiene varios niveles de `EXPLAIN`:
```sql
EXPLAIN SYNTAX SELECT ...; -- cómo entiende la consulta
EXPLAIN PLAN SELECT ...; -- plan lógico
EXPLAIN PIPELINE SELECT ...; -- plan de ejecución paralelo
EXPLAIN ESTIMATE SELECT ...; -- filas y *parts* escaneados
```
Y para ver lo que realmente pasó:
```sql
SELECT
query,
query_duration_ms,
read_rows,
read_bytes,
memory_usage,
result_rows
FROM system.query_log
WHERE event_time >= now() - INTERVAL 10 MINUTE
AND type = 'QueryFinish'
ORDER BY query_duration_ms DESC
LIMIT 20;
```
`system.query_log` es el mejor amigo cuando intentas averiguar por qué una consulta va más lenta de lo que debería.
## Un ejemplo realista
Dashboard de tráfico de un blog, una sola consulta:
```sql
SELECT
toStartOfDay(ts) AS day,
count() AS views,
uniq(user_id) AS visitors,
countIf(referrer = 'direct') AS direct_views,
quantile(0.95)(duration_ms) AS p95_ms,
topK(5)(path) AS top_paths
FROM pageviews
WHERE ts >= today() - INTERVAL 30 DAY
GROUP BY day
ORDER BY day WITH FILL STEP INTERVAL 1 DAY;
```
Sobre 100 millones de filas, esto devuelve en decenas de milisegundos si la tabla está bien diseñada.
## Por dónde seguir
- **[IV: materialized views, projections y TTL](/post/clickhouse-desde-cero-iv-materialized-views-projections-y-ttl)** — cómo conseguir que una consulta que escanea 100M filas se responda desde 30 filas pre-agregadas.
- **[V: producción, replicación y clusters](/post/clickhouse-desde-cero-v-produccion-replicacion-y-clusters)** — operar ClickHouse cuando el negocio depende de él.
¿Te perdiste los fundamentos? Vuelve a [I: instalación y primeros pasos](/post/clickhouse-desde-cero-i-instalacion-y-primeros-pasos) o a [II: tipos de datos y MergeTree](/post/clickhouse-desde-cero-ii-tipos-de-datos-y-mergetree).
---
# Una cena con amigos vale más que diez meetings
URL: https://javiervalencia.net/post/una-cena-con-amigos-vale-mas-que-diez-meetings
Hace un par de años empecé a notar un patrón extraño. Cada vez que tenía un problema importante en el trabajo (una decisión complicada, un cliente difícil, una duda sobre si cambiar de dirección en un proyecto), terminaba resolviéndolo no en la oficina, ni en una reunión, ni en un café con un colega, sino en una cena larga con amigos que no tenían nada que ver con ese problema. Dos copas de vino, dos horas de conversación sobre cualquier otra cosa, y de repente sabía qué hacer.
Este post es sobre por qué pasa eso y por qué creo que subestimamos enormemente las cenas con amigos como herramienta de pensamiento.
## El problema con los meetings

Un meeting, por definición, es un formato optimizado para alcanzar decisiones rápidas. Tiene un orden del día, un tiempo limitado, unos asistentes relevantes, un objetivo concreto. Todo el formato empuja a ir al grano.
Esto suena bien y en casos concretos lo es. Pero tiene un coste enorme: elimina el pensamiento que no parece productivo en el momento. En un meeting no puedes pararte a hablar veinte minutos sobre algo tangencial. No puedes contar una anécdota aparentemente irrelevante que, al final, ilustra el problema desde otro ángulo. No puedes dejar que la conversación vague. Y es precisamente en esa divagación donde pasan las cosas importantes.
Los meetings convergen demasiado pronto. Y los problemas difíciles rara vez se resuelven con convergencia rápida.
## Lo que pasa en una cena larga
Una cena con amigos es casi lo opuesto. Si la haces bien (tres o cuatro personas, tres horas de mesa, vino ligero, sin prisa), pasan cosas que no pasan en ningún otro contexto.
La primera es el **ancho de banda emocional**. En un meeting estás concentrado en el problema. En una cena estás concentrado en las personas. Esto cambia radicalmente lo que dices y cómo lo escuchas. Cuentas las cosas con más matices, con más contexto, con más duda. Y los demás te escuchan con más apertura, no como oponentes en una negociación sino como gente que te quiere bien.
La segunda es el **cambio de perspectiva**. Tus amigos cercanos rara vez trabajan en tu industria, tienen tu rol o se enfrentan a tus problemas. Cuando les cuentas algo, te obliga a explicarlo desde cero, sin jerga, sin supuestos compartidos. Y explicar algo a alguien que no comparte tu contexto es una de las mejores maneras de verlo por primera vez.
La tercera es la **ausencia de agenda oculta**. En un meeting todo el mundo tiene intereses. Quien pregunta puede estar posicionándose. Quien responde puede estar defendiéndose. En una cena con amigos, nadie tiene nada que ganar ni perder. Las preguntas son genuinas. Las respuestas son honestas.
## El poder del comentario lateral

Lo que más me ha sorprendido de las cenas con amigos es el valor de lo que no pides.
Cuentas un problema. La conversación se mueve a otra cosa. Dos horas después, alguien vuelve a lo primero con un comentario aparentemente trivial: "Lo de lo que nos contabas antes, ¿no pasará que…?". Y ese comentario, que no ha venido de alguien que conozca tu campo, que no ha sido pensado como consejo, es exactamente el ángulo que tú no veías.
He tomado decisiones importantes basadas en comentarios laterales que los amigos me han hecho en cenas. Cambios de dirección profesional, renuncias, aceptaciones. Ninguna de esas decisiones vino de un consejo directo. Todas vinieron de un comentario casi casual que me devolvió una verdad que ya sabía pero que no había querido admitir.
Los amigos cercanos tienen una capacidad rara: pueden ver las cosas que te cuentas a ti mismo y que son inconsistentes con lo que realmente quieres. En un meeting, nadie te hace de espejo. En una cena, sí.
## Por qué la comida importa
Podrías replicar parte de esto en una videollamada larga. Pero no es lo mismo, y la diferencia está en el cuerpo.
Comer juntos es un acto físico que compartes. Brindas, cortas, pasas el pan, compartes el postre. Hay micro-interacciones constantes que no son conversación pero que sostienen la conversación. Tu cerebro percibe que estás en un acto social completo, no en un intercambio de información. Eso baja las defensas.
Además, una cena tiene un ritmo propio que ninguna videollamada puede tener. Los silencios no son incómodos: son el momento en que alguien está masticando. Las pausas largas son normales. La conversación avanza despacio, vuelve hacia atrás, retoma un hilo, lo deja. Es un flujo natural que permite el pensamiento lento que los formatos digitales mataron.
Y hay algo más, difícil de articular pero real: compartir comida genera compromiso. Cuando has pasado tres horas cenando con alguien, te importa lo que le pasa. Los meetings por Zoom no generan nada parecido, aunque sean con las mismas personas durante el mismo tiempo.
## Lo que no funciona

He visto a mucha gente intentar replicar este tipo de conversaciones con formatos que no funcionan:
**Cenas de trabajo.** Si el objetivo declarado de la cena es hablar de trabajo, se convierten en un meeting con vino. Los amigos de trabajo son amigos en un sentido restringido; no pueden desempeñar el papel de alguien completamente externo. Las mejores cenas son con gente que no se cruza con tu vida profesional.
**Grupos grandes.** A partir de cinco personas la conversación se fragmenta. Hay subgrupos, hay alguien dominando el centro, hay gente que se queda callada. Cuatro personas es el máximo para una conversación realmente profunda.
**Restaurantes ruidosos.** El sonido importa. Un restaurante donde tienes que alzar la voz mata la conversación sutil. Los mejores sitios para cenar con amigos son los pequeños, con poca gente, donde puedes hablar sin forzar.
**Cenas cortas.** Menos de dos horas y apenas has superado los chismes. Las conversaciones interesantes empiezan cuando llevas noventa minutos de cena. Si tienes que marcharte a las once para coger el último metro, no ha merecido la pena. Organiza de forma que puedas quedarte hasta cuando la conversación pida.
## Cómo las sostengo en el calendario
Con amigos concretos he adoptado una rotación trimestral: cada tres meses, cena fija, tres personas, mismo restaurante, misma hora. No es cada semana, no es cada mes. Cada trimestre, cuatro veces al año. Con eso es suficiente para mantener la profundidad sin sobrecargar el calendario.
Esto tiene una ventaja: la cena se convierte en un punto de referencia temporal. "Desde la última cena hasta ahora ha pasado X, Y, Z." Es un marcador que ordena los últimos meses. Y además te obliga a acumular temas: cosas que querías hablar con ellos y que has ido guardando mentalmente.
## El coste comparativo
Una cena buena cuesta entre cuarenta y sesenta euros por persona. Tres horas de tu tiempo, más el desplazamiento. En total, unas cuatro horas.
Un consultor senior que te aconseje profesionalmente tres horas te cobra entre seiscientos y mil quinientos euros. No va a entenderte como un amigo que te conoce desde hace veinte años, no va a tener la capacidad de ver los comentarios laterales que te desbloquean, no va a darte feedback honesto sino el que crees que quieres oír. Y lo pagas mucho más caro.
No estoy diciendo que los amigos sustituyan a los consultores. Tienen funciones distintas. Estoy diciendo que, para los problemas que más me importan, una cena con tres amigos cercanos ha sido una herramienta más potente que casi cualquier otra. Y el coste es ridículamente bajo.
## Para acabar
Si tuviera que recomendar una sola inversión a alguien que está atascado en algo, personal o profesional, sería esta: organizar una cena con tres amigos que no trabajen en su campo, en un sitio tranquilo, con tiempo de sobra, sin agenda. No hace falta pedir consejo. Solo hace falta estar. La conversación hará el resto.
Los meetings resuelven tareas. Las cenas con amigos resuelven vidas. Y las vidas se resuelven despacio, con gente que te quiere, en mesas largas, con tiempo.
---
# Prometheus y Grafana para servicios pequeños
URL: https://javiervalencia.net/post/prometheus-y-grafana-para-servicios-pequenos
Cuando se habla de Prometheus y Grafana casi siempre es en el contexto de Kubernetes, de clusters con cientos de nodos, de arquitecturas de observabilidad con Thanos, Cortex, Mimir, Loki, Tempo y otros nombres tomados de la mitología griega. Todo eso existe y tiene su sitio, pero hay una realidad más modesta que también merece atención: el desarrollador o sysadmin con dos o tres VPS y un puñado de servicios, que necesita saber qué está pasando sin montar una plataforma entera.
Este post cubre exactamente eso. Cómo instalar, configurar y aprovechar Prometheus y Grafana en una VPS para monitorizar tu blog, tu API, tu base de datos y tu servidor, sin complicaciones.
## Por qué Prometheus y no otra cosa

Existen alternativas: InfluxDB, Datadog, New Relic, Zabbix, incluso scripts caseros con cron y correo. Cada una tiene su hueco. Prometheus gana en los proyectos pequeños por tres razones concretas.
La primera es que es gratuito y autoalojado. Sin costes por host, sin métricas "premium", sin límites de retención. Todo corre en tu máquina, todos los datos son tuyos.
La segunda es el modelo de *pull*. Prometheus es quien pregunta a tus servicios cada X segundos "dame tus métricas", no al revés. Esto elimina toda una clase de problemas de seguridad y configuración: tus servicios no tienen que saber dónde vive el colector ni tener credenciales para escribir en él.
La tercera es la cantidad de exporters disponibles. Hay un exporter para casi cualquier cosa: `node_exporter` para el sistema, `postgres_exporter` para PostgreSQL, `nginx_exporter` para Nginx, `blackbox_exporter` para hacer pings HTTP. Conectar un servicio nuevo suele ser cuestión de minutos.
## Arquitectura mínima
Para una VPS, la arquitectura que recomiendo es esta:
```
┌──────────────────────────────────────────────┐
│ VPS (la misma donde corre tu servicio) │
│ │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ node_exporter│←────┤ │ │
│ └─────────────┘ │ │ │
│ │ Prometheus │ │
│ ┌─────────────┐ │ (puerto 9090) │ │
│ │ tu app │←─────┤ │ │
│ │ /metrics │ └────────┬────────┘ │
│ └─────────────┘ │ │
│ ↓ │
│ ┌──────────────┐ │
│ │ Grafana │ │
│ │ (puerto 3000)│ │
│ └──────────────┘ │
└──────────────────────────────────────────────┘
```
Todo en la misma máquina. Prometheus se conecta a los exporters localmente, Grafana consulta a Prometheus, y expones Grafana por HTTPS con el reverse proxy que ya tengas.
Para dos o tres VPS la variante es: un Prometheus central que consulta a los exporters de todas las máquinas. Nada más hasta que llegas a una escala que ya no es "pequeña".
## Instalación con systemd, sin Docker

En Docker es trivial, pero si no estás usando Docker en tus VPS, no tiene sentido meterlo solo para esto. La instalación nativa es igual de simple.
Descarga el binario de Prometheus:
```bash
wget https://github.com/prometheus/prometheus/releases/download/v2.52.0/prometheus-2.52.0.linux-amd64.tar.gz
tar xzf prometheus-2.52.0.linux-amd64.tar.gz
sudo mv prometheus-2.52.0.linux-amd64/prometheus /usr/local/bin/
sudo mv prometheus-2.52.0.linux-amd64/promtool /usr/local/bin/
sudo useradd --no-create-home --shell /bin/false prometheus
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo chown prometheus:prometheus /var/lib/prometheus
```
Un unit file de systemd razonable:
```ini
[Unit]
Description=Prometheus
After=network-online.target
Wants=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--storage.tsdb.retention.time=30d \
--web.listen-address=127.0.0.1:9090
Restart=on-failure
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/lib/prometheus
ProtectHome=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
```
Dos detalles importantes: retención a 30 días (por defecto son 15, en un servicio pequeño 30-90 días suelen sobrar), y escucha solo en localhost. No quieres exponer Prometheus a internet.
Grafana tiene paquete oficial en APT, así que en Debian/Ubuntu es:
```bash
sudo apt install -y apt-transport-https software-properties-common
sudo wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key
echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install grafana
sudo systemctl enable --now grafana-server
```
Grafana escucha por defecto en `:3000`. Ponle un reverse proxy delante con Nginx y HTTPS, y autenticación básica si no vas a usar la de Grafana.
## Configuración de Prometheus
Un `prometheus.yml` mínimo para un setup pequeño:
```yaml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node'
static_configs:
- targets: ['localhost:9100']
- job_name: 'blog'
static_configs:
- targets: ['localhost:8080']
metrics_path: /metrics
```
`scrape_interval` a 15 segundos es un buen compromiso: suficiente para detectar problemas rápido, no tanto como para generar gigas de datos innecesarios.
Recarga Prometheus con un SIGHUP (`systemctl reload prometheus`) para que aplique cambios de configuración sin perder histórico.
## node_exporter: lo que realmente importa

De todos los exporters, `node_exporter` es el imprescindible. Te da CPU, memoria, disco, red, load average, temperatura, procesos... todo lo que necesitas para saber si tu máquina está bien.
```bash
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.0/node_exporter-1.8.0.linux-amd64.tar.gz
tar xzf node_exporter-1.8.0.linux-amd64.tar.gz
sudo mv node_exporter-1.8.0.linux-amd64/node_exporter /usr/local/bin/
```
Con un unit file similar al de Prometheus, `ExecStart=/usr/local/bin/node_exporter --web.listen-address=127.0.0.1:9100`. Hecho.
## Exponer métricas desde tu propia app
Si tu aplicación es en Go, el cliente oficial es trivial:
```go
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var requestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP requests by status",
},
[]string{"path", "status"},
)
func init() {
prometheus.MustRegister(requestsTotal)
}
func main() {
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}
```
Incrementa el contador en tus handlers y ya tienes métricas. Lo mismo para otros lenguajes: hay librerías oficiales para Python, Ruby, Node, Rust, Java.
Las cuatro métricas básicas que recomiendo exponer desde el principio:
- `http_requests_total{path, status}`: contador de peticiones por ruta y código.
- `http_request_duration_seconds`: histograma de latencias.
- `db_queries_total{status}`: contador de queries a BD.
- `background_jobs_total{job, status}`: trabajos en segundo plano si los hay.
Con estas cuatro ya puedes construir los paneles esenciales: tráfico, latencia, errores y salud de tareas async.
## Queries PromQL que vas a usar
PromQL tiene fama de enrevesado pero el 90% del tiempo usas cuatro patrones.
**Tasa de peticiones por segundo:**
```promql
sum by (path) (rate(http_requests_total[5m]))
```
**Porcentaje de errores:**
```promql
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
```
**Percentil 95 de latencia:**
```promql
histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
```
**Uso de memoria del sistema:**
```promql
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes
```
Con esto cubres el 80% de los paneles que tendrás en tu dashboard.
## Dashboards de Grafana: importa primero, retoca después
No empieces desde cero. Grafana tiene un repositorio público con miles de dashboards ya hechos. Para node_exporter, el dashboard `1860` es excelente: lo importas con un `ID` en Grafana y tienes CPU, memoria, disco y red listos en dos minutos.
Haz lo mismo para PostgreSQL (`9628`), Nginx (`12708`), Redis (`11835`). El 80% de lo que vas a querer ver ya está diseñado, probado y mantenido por alguien. Retoca después lo que te falta, pero no reinventes.
## Alertas: Alertmanager o un script
Prometheus puede generar alertas y mandarlas a Alertmanager, que se encarga de enviarlas por email, Slack, Telegram o lo que quieras. Para un setup pequeño, Alertmanager puede ser overkill.
Alternativa sencilla: un cron cada cinco minutos que consulta la API de Prometheus y manda un correo si algo está mal. Con `curl` y `jq` lo haces en veinte líneas. Cuando te canse, pasas a Alertmanager. Mientras tanto, funciona.
Tres alertas básicas para empezar:
1. El servicio responde con 5XX más del 1% del tráfico durante más de 5 minutos.
2. La latencia p95 sube por encima del doble de lo habitual durante 10 minutos.
3. El disco está por encima del 85% de uso.
Con estas tres ya te enteras de la mayoría de problemas serios antes de que tus usuarios te escriban.
## Lo que no necesitas (todavía)
Hay cosas que la industria asume como estándar y que en un setup pequeño son ruido:
- **Thanos / Cortex**: escalabilidad horizontal y retención multi-año. No los necesitas hasta que tengas múltiples Prometheus.
- **Loki para logs**: si tu servicio cabe en una VPS, `journalctl` es suficiente.
- **Tracing distribuido**: útil en microservicios. Si tienes un monolito, una buena logging estructurada es más práctica.
- **Service mesh**: si tienes que preguntarte si lo necesitas, no lo necesitas.
Añade estas piezas cuando tengas un problema concreto que no puedes resolver con lo que ya hay. No antes.
## Conclusión
Prometheus y Grafana no son herramientas exclusivas de grandes empresas. Con una VPS, media hora y un par de dashboards importados tienes más visibilidad de tu servicio que el 90% de los proyectos pequeños que conozco, que viven a ciegas hasta que algo falla y entonces revisan logs.
La inversión inicial es pequeña. El retorno, cada vez que te enteras de que un disco se está llenando antes de que se llene, que la latencia está subiendo antes de que los usuarios lo noten, que una query nueva está matando la base de datos, justifica con creces ese esfuerzo inicial. Observabilidad no es un lujo de escala: es una red de seguridad que cualquier proyecto serio debería tener desde el primer mes.
---
# Makefiles en proyectos Go: lo justo y necesario
URL: https://javiervalencia.net/post/makefiles-en-proyectos-go
Go tiene `go build`, `go test`, `go run`. No necesita un sistema de build. Pero en cuanto tu proyecto crece un poco, acabas con comandos que no son triviales de recordar: flags de compilación, variables de entorno para tests, linters, generación de código, deploys. Un Makefile es la forma más simple de documentar y ejecutar esos comandos.
## El Makefile mínimo

```makefile
.PHONY: build test lint run clean
build:
go build -o bin/server ./cmd/server
test:
go test ./...
lint:
golangci-lint run
run:
API_TOKEN=dev go run ./cmd/server
clean:
rm -rf bin/ tmp/
```
Cinco targets. Cinco comandos. Nada más. Con esto puedes hacer `make build`, `make test`, `make run` y `make lint` sin recordar nada. El `.PHONY` le dice a Make que estos targets no son ficheros sino acciones.
## Variables
Si repites valores, usa variables:
```makefile
BINARY := bin/server
CMD := ./cmd/server
GO := go
.PHONY: build test lint run
build:
$(GO) build -o $(BINARY) $(CMD)
test:
$(GO) test ./...
lint:
golangci-lint run
run: build
API_TOKEN=dev $(BINARY)
```
Nota que `run` depende de `build`. Cuando haces `make run`, primero compila y luego ejecuta. Make solo recompila si los fuentes han cambiado.
## Información del build

Puedes inyectar información en el binario en tiempo de compilación con `-ldflags`:
```makefile
VERSION := $(shell git describe --tags --always --dirty)
COMMIT := $(shell git rev-parse --short HEAD)
BUILD_DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildDate=$(BUILD_DATE)
build:
go build -ldflags "$(LDFLAGS)" -o bin/server ./cmd/server
```
En tu `main.go`:
```go
var (
version = "dev"
commit = "none"
buildDate = "unknown"
)
func main() {
slog.Info("starting", "version", version, "commit", commit, "build_date", buildDate)
// ...
}
```
Cada binario compilado sabe exactamente de qué commit viene y cuándo se compiló. Invaluable para debugging en producción.
## Tests con cobertura
```makefile
test:
go test ./... -count=1
cover:
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
@echo "Abierto coverage.html en el navegador"
test-race:
go test ./... -race -count=1
```
`-count=1` desactiva la cache de tests para que siempre se ejecuten. `-race` activa el detector de race conditions, que es lento pero encuentra bugs de concurrencia que de otra forma son invisibles.
## Deploy

```makefile
SERVER := root@web.javiervalencia.net
DEPLOY_PATH := /opt/blog
deploy-dry:
rsync -avzn --delete \
--exclude='.git' --exclude='*.go' --exclude='go.*' \
--exclude='cmd' --exclude='internal' --exclude='deploy' \
--exclude='.env' --exclude='content/views.json' \
./ $(SERVER):$(DEPLOY_PATH)/
deploy: deploy-dry
@read -p "¿Continuar con el deploy? (s/n) " ans; \
if [ "$$ans" = "s" ]; then \
rsync -avz --delete \
--exclude='.git' --exclude='*.go' --exclude='go.*' \
--exclude='cmd' --exclude='internal' --exclude='deploy' \
--exclude='.env' --exclude='content/views.json' \
./ $(SERVER):$(DEPLOY_PATH)/; \
ssh $(SERVER) 'systemctl restart blog'; \
echo "Deploy completado."; \
fi
reload:
curl -s -X POST -H "Authorization: Bearer $${API_TOKEN}" \
https://javiervalencia.net/api/v1/reload | jq .
```
`make deploy` primero hace un dry-run, luego pide confirmación. Si dices sí, sincroniza y reinicia el servicio. `make reload` recarga el contenido sin reiniciar, útil cuando solo has subido un post nuevo.
## Docker (si lo necesitas)
```makefile
DOCKER_IMAGE := blog
DOCKER_TAG := $(VERSION)
docker-build:
docker build -t $(DOCKER_IMAGE):$(DOCKER_TAG) .
docker tag $(DOCKER_IMAGE):$(DOCKER_TAG) $(DOCKER_IMAGE):latest
docker-run:
docker run --rm -p 8080:8080 --env-file .env $(DOCKER_IMAGE):latest
```
No uso Docker para este blog, pero si tu proyecto lo necesita, tener los comandos en el Makefile evita recordar las flags de `docker build` cada vez.
## Help automático
Un truco útil: generar la ayuda automáticamente a partir de comentarios:
```makefile
.DEFAULT_GOAL := help
help: ## Mostrar esta ayuda
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
build: ## Compilar el binario
go build -o bin/server ./cmd/server
test: ## Ejecutar tests
go test ./...
lint: ## Ejecutar linter
golangci-lint run
run: ## Arrancar en desarrollo
API_TOKEN=dev go run ./cmd/server
deploy: ## Desplegar a producción
# ...
```
Ahora `make` sin argumentos muestra la lista de targets con su descripción. Es documentación que se mantiene sola.
## Lo que NO debe hacer un Makefile
He visto Makefiles de 500 líneas con lógica condicional, funciones de shell anidadas, variables computadas con tres niveles de indirección y targets que nadie recuerda para qué sirven. Eso no es un Makefile: es un script de shell disfrazado.
Un buen Makefile para un proyecto Go debería:
- Caber en una pantalla (máximo 50-60 líneas).
- Tener targets que un humano pueda recordar.
- No duplicar lo que Go ya hace bien (`go generate`, `go vet`, `go mod tidy`).
- No tener lógica compleja. Si necesitas un `if` dentro de un target, probablemente necesitas un script de shell separado.
El Makefile es un índice de comandos, no un sistema de build. Go ya tiene un sistema de build excelente. El Makefile solo le pone nombres fáciles de recordar a los comandos que usas todos los días.
## Conclusión
Un Makefile de 30 líneas cubre el 90% de lo que necesitas en un proyecto Go. Build, test, lint, run, deploy. Lo justo. Ni más ni menos. Cada target es un comando que puedes leer y entender en un segundo. Eso es lo que debería ser un Makefile: obvio, predecible y útil.
---
# ClickHouse desde cero (II): tipos de datos y MergeTree
URL: https://javiervalencia.net/post/clickhouse-desde-cero-ii-tipos-de-datos-y-mergetree
*Segunda entrega de la serie **[ClickHouse desde cero a pro](/search?tag=clickhouse-desde-cero)**. Tiempo de lectura estimado: 11 minutos.*
En la [entrega anterior](/post/clickhouse-desde-cero-i-instalacion-y-primeros-pasos) levantamos un ClickHouse local, creamos una tabla con `MergeTree` y metimos un millón de filas. En este post vamos al grano: qué tipos de datos existen, por qué los *strings de baja cardinalidad* son especiales, y cómo funciona realmente el motor `MergeTree` por dentro.
Si entiendes estas dos cosas (tipos y `MergeTree`), ya estás por encima del 80% de la gente que usa ClickHouse a diario.
## El sistema de tipos
ClickHouse tiene un sistema de tipos estático y fuerte, con más granularidad que PostgreSQL. Elegir el tipo correcto no es cosmético: impacta en el tamaño en disco, en la velocidad de lectura y en la memoria usada durante las consultas.
### Enteros
```sql
Int8 -- -128 a 127
Int16 -- -32.768 a 32.767
Int32 -- ~ ±2.100 millones
Int64 -- ~ ±9 trillones
Int128, Int256
UInt8, UInt16, UInt32, UInt64, UInt128, UInt256 -- sin signo
```
Usa el más pequeño que te quepa. Un `UInt8` ocupa 1 byte por fila. Un `UInt64`, 8. En una tabla de 10.000 millones de filas, esa diferencia son **70 GB**. ClickHouse comprime, sí, pero un tipo pequeño comprime aún mejor.
### Decimales y floats
```sql
Float32, Float64 -- para métricas donde no importa la precisión exacta
Decimal(P, S) -- para dinero, siempre
Decimal32(S), Decimal64(S), Decimal128(S)
```
`Decimal(18, 4)` significa hasta 18 dígitos en total, 4 después de la coma. Cualquier cosa que sea dinero o que pase por una API pública debería ser `Decimal`, no `Float`.
### Strings
```sql
String -- variable, UTF-8, sin límite
FixedString(N) -- N bytes exactos (útil para hashes, IPs binarias)
LowCardinality(String) -- string diccionarizado
```
`LowCardinality` merece una sección aparte. Es, probablemente, la optimización más rentable y peor conocida de ClickHouse.
### Fechas y tiempos
```sql
Date -- día, 2 bytes, hasta 2149
Date32 -- día, 4 bytes, hasta 2299
DateTime -- segundos desde epoch, 4 bytes
DateTime64(precision, timezone) -- hasta nanosegundos, 8 bytes
```
Una lección aprendida a golpes: usa siempre `DateTime` o `DateTime64` con **timezone explícita** si tu aplicación cruza husos horarios. Ahorra bugs raros.
```sql
CREATE TABLE events (
ts DateTime64(3, 'UTC'),
...
) ENGINE = MergeTree() ORDER BY ts;
```
### Tipos compuestos
```sql
Array(T) -- array homogéneo: Array(String), Array(UInt32)
Tuple(T1, T2, ...) -- tupla heterogénea
Map(K, V) -- mapa clave-valor
Nested(...) -- sub-tabla embebida
Nullable(T) -- cualquier tipo con soporte de NULL
JSON -- tipo JSON dinámico (desde 24.x)
```
Los arrays son **de primera clase** en ClickHouse. No es un añadido como en PostgreSQL: son idiomáticos. Verás muchísimos arrays en la [entrega III](/post/clickhouse-desde-cero-iii-consultas-analiticas-en-profundidad).
### Nullable: úsalo con cuidado
`Nullable(T)` añade una columna extra interna (bitmap de nulos) y desactiva varias optimizaciones. Si puedes, representa la ausencia con un valor centinela:
```sql
-- Evita esto si puedes
customer_id Nullable(UInt64)
-- Prefiere esto
customer_id UInt64 DEFAULT 0
```
Obviamente no siempre es posible, pero la regla general es: **no hagas `Nullable` por reflejo**.
## LowCardinality: la optimización que nadie te cuenta
Un `String` en ClickHouse se almacena tal cual: cada fila guarda sus bytes en disco. Para 200 millones de filas con un campo `country = 'ES'`, eso son 200 millones de copias de `'ES'`.
`LowCardinality(String)` cambia eso: guarda un **diccionario** de valores únicos y en cada fila pone solo un índice (normalmente un `UInt8` o `UInt16`). Es básicamente un enum construido automáticamente.
Beneficios:
- La columna en disco ocupa muy poco (bytes por fila en vez de cadenas).
- Los `GROUP BY` y `WHERE` sobre esa columna son **mucho más rápidos** porque se operan sobre índices numéricos.
- No tienes que declarar los valores posibles por adelantado, como harías con un `Enum`.
Cuándo usarlo:
- Países, idiomas, dispositivos, navegadores, estados, tipos...
- Cualquier string con menos de ~10.000 valores distintos.
Cuándo **no** usarlo:
- UUIDs, rutas únicas, emails, cualquier cosa con alta cardinalidad. El diccionario crece sin control y pierde la ventaja.
Regla práctica: si al hacer `SELECT uniqExact(columna) FROM tabla` sale menos de unos pocos miles, `LowCardinality` es tu amigo.
## El motor MergeTree
ClickHouse tiene docenas de *table engines*. El que vas a usar casi siempre es `MergeTree` o alguno de sus derivados (`ReplacingMergeTree`, `SummingMergeTree`, `AggregatingMergeTree`, `ReplicatedMergeTree`).
Entender cómo funciona internamente es clave para diseñar tablas rápidas.
### Parts
Cuando haces un `INSERT`, ClickHouse escribe los datos en una carpeta nueva en disco llamada **part**. Un *part* es inmutable: nunca se modifica después de crearse. Cada columna dentro del part es un fichero separado (.bin + .mrk para el índice de marks).
Cada `INSERT` produce uno o más *parts*. Esto explica por qué insertar fila a fila es un desastre: generas miles de *parts* pequeñas que el motor luego tiene que fusionar.
### Merges
En background, ClickHouse va **mergeando** *parts* pequeñas en otras más grandes. De ahí el nombre del motor. Un merge lee varios *parts* ordenados, los combina, y escribe una *part* nueva. Cuando acaba, borra las antiguas.
Puedes ver los merges en curso:
```sql
SELECT * FROM system.merges;
```
Y las *parts* activas de una tabla:
```sql
SELECT
partition,
name,
rows,
bytes_on_disk,
modification_time
FROM system.parts
WHERE table = 'pageviews' AND active
ORDER BY modification_time DESC;
```
### ORDER BY: la clave de ordenación
La cláusula `ORDER BY` en la definición de la tabla determina **cómo se ordenan los datos dentro de cada part**. Esa ordenación es la que hace rápidas las consultas con filtros.
```sql
CREATE TABLE calls (
ts DateTime,
country LowCardinality(String),
customer UInt64,
duration UInt16
)
ENGINE = MergeTree()
ORDER BY (country, ts);
```
Si la mayoría de consultas filtran por país primero y luego por rango de tiempo, esta `ORDER BY` es ideal: los datos de un mismo país quedan contiguos en disco y el índice esparcido (*sparse primary index*) permite saltar directamente al rango.
Si invertías las consultas (filtras por `ts` casi siempre, rara vez por `country`), la mejor elección sería `ORDER BY (ts, country)`.
Regla práctica: **los campos más selectivos y más usados en `WHERE` van primero**.
### PRIMARY KEY vs ORDER BY
En ClickHouse, `PRIMARY KEY` **no** significa unicidad. Significa "el prefijo de la clave de ordenación que se indexa". Por defecto, `PRIMARY KEY = ORDER BY`.
Puedes separarlas si quieres ordenar por muchos campos pero indexar solo algunos:
```sql
ORDER BY (country, city, ts, customer_id)
PRIMARY KEY (country, city)
```
Esto reduce el tamaño del índice en memoria.
### PARTITION BY
`PARTITION BY` divide físicamente la tabla en particiones independientes. No es lo mismo que en PostgreSQL: aquí **no** mejora las lecturas por sí solo (la clave de ordenación ya lo hace). Lo que hace es:
- Permitir operaciones en bloque por partición (`DROP PARTITION`, `DETACH PARTITION`).
- Separar datos que rara vez se consultan juntos.
- Facilitar la expiración por TTL, que veremos en la [entrega IV](/post/clickhouse-desde-cero-iv-materialized-views-projections-y-ttl).
Casi siempre se particiona por mes:
```sql
ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (country, ts);
```
**No te pases particionando.** Una partición por día sobre tres años son más de 1.000 particiones: demasiadas, afecta al rendimiento. Mes es el estándar para la mayoría de casos. Diaria solo si retienes pocos meses.
## Variantes útiles de MergeTree
### ReplacingMergeTree
Desduplica filas con la misma `ORDER BY` durante los merges. No es instantáneo: la desduplicación ocurre cuando el motor fusiona las *parts*.
```sql
CREATE TABLE users (
id UInt64,
email String,
updated_at DateTime
)
ENGINE = ReplacingMergeTree(updated_at)
ORDER BY id;
```
Útil para tablas de "última versión conocida". Pero ojo: para consultar solo la última versión, necesitas `FINAL`:
```sql
SELECT * FROM users FINAL WHERE id = 42;
```
`FINAL` tiene coste. Para tablas grandes existe `SELECT ... FROM users WHERE id = 42 ORDER BY updated_at DESC LIMIT 1 BY id`.
### SummingMergeTree
Suma automáticamente las columnas numéricas cuando fusiona filas con la misma `ORDER BY`. Útil para pre-agregaciones simples.
### AggregatingMergeTree
La más potente de las tres: permite usar cualquier función de agregación, no solo suma. Es la base de los *materialized views* complejos que veremos en la [entrega IV](/post/clickhouse-desde-cero-iv-materialized-views-projections-y-ttl).
## Un ejemplo completo
Diseñamos una tabla de CDRs (registros de llamadas), que es un caso típico donde ClickHouse brilla:
```sql
CREATE TABLE cdrs (
ts DateTime64(3, 'UTC'),
customer_id UInt32,
callee String,
caller String,
country LowCardinality(String),
duration_ms UInt32,
cost_eur Decimal(12, 6),
disposition LowCardinality(String),
sip_code UInt16
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (customer_id, ts)
TTL toDateTime(ts) + INTERVAL 36 MONTH;
```
Decisiones aquí:
- `customer_id` primero en `ORDER BY` porque **casi todas** las consultas filtran por cliente.
- `ts` segundo para que los rangos temporales de un cliente sean contiguos.
- Partición mensual.
- `TTL` de 36 meses: después, los datos se borran solos. Veremos TTL en detalle en la [entrega IV](/post/clickhouse-desde-cero-iv-materialized-views-projections-y-ttl).
- `LowCardinality` en `country` y `disposition` (ANSWERED, BUSY, NO ANSWER, FAILED).
## Por dónde seguir
- **[III: consultas analíticas en profundidad](/post/clickhouse-desde-cero-iii-consultas-analiticas-en-profundidad)** — ahora que tienes tablas bien diseñadas, toca exprimir el lenguaje.
- **[IV: materialized views, projections y TTL](/post/clickhouse-desde-cero-iv-materialized-views-projections-y-ttl)** — pre-agregaciones automáticas y gestión del ciclo de vida del dato.
- **[V: producción, replicación y clusters](/post/clickhouse-desde-cero-v-produccion-replicacion-y-clusters)** — lo que necesitas saber cuando dejas de hacer pruebas y esto se convierte en un servicio crítico.
Y si llegas tarde a la serie, empieza por la [entrega I: instalación y primeros pasos](/post/clickhouse-desde-cero-i-instalacion-y-primeros-pasos).
---
# Chiringuitos de Fuengirola: los que no cambio
URL: https://javiervalencia.net/post/chiringuitos-de-fuengirola-los-que-no-cambio
Fuengirola tiene ocho kilómetros de paseo marítimo y, a lo largo de esos ocho kilómetros, una cantidad absurda de chiringuitos. Van desde los que sirven paella de sobre a cuarenta euros hasta los que tienen el mejor espeto de sardinas de la provincia. Distinguir unos de otros no es fácil si no vives aquí. Este post es mi intento de resumir lo que he aprendido después de años comiendo en la playa, con los favoritos que no cambio y los errores que te evitas si lees hasta el final.
## Qué es un chiringuito de verdad

Antes de nada, una definición útil. Un chiringuito, en el sentido en que se usa la palabra en la Costa del Sol, es un restaurante a pie de playa con terraza sobre la arena, especializado en productos del mar cocinados a la brasa o al fuego en barca. El elemento distintivo de los chiringuitos buenos es la **espetera**: una barca varada en la arena con leña encendida donde se asan los espetos (sardinas ensartadas en una caña).
Cualquier sitio en la playa que no tenga espetera encendida no es un chiringuito auténtico. Es un restaurante de playa. La diferencia importa, porque el espeto de sardinas es el producto estrella y cocinarlo bien requiere fuego de olivo, cañas de caña de azúcar y un espetero con experiencia. Los tres elementos cuestan dinero y paciencia, y los sitios que los tienen son una minoría.
## El Cachalote: el clásico que sigue siendo clásico
Lo pongo primero porque es el que más me gusta. Está en Los Boliches, pegado al paseo pero con acceso directo a la arena. No es grande, no tiene grandes letreros, y si vas en julio un domingo sin reservar te quedas fuera.
Pido siempre lo mismo: espetos de sardinas, un boquerón en adobo, una ensaladilla rusa y una caña. Los espetos cuestan unos quince euros las seis sardinas. El precio ha subido en los últimos años, como todo, pero la calidad no ha bajado. Las sardinas son frescas, de la lonja de Fuengirola casi siempre, y el punto de la brasa es el que tiene que ser: piel crujiente, carne jugosa, sal gorda por encima que se queda pegada en la piel.
La ensaladilla es otra seña de identidad del sitio. Es casera, con mayonesa hecha con aceite de oliva suave, atún del bueno, patata del punto. Cuando sirven las raciones todavía están templadas, cosa que muchos sitios no hacen. Cuatro euros y medio la ración.
## La Cucharita: para el pescado frito

Si lo que quieres es una fritura de pescado clásica, no un espeto, mi parada es **La Cucharita**, hacia el lado oeste del paseo, cerca del río Fuengirola. Es un chiringuito pequeño, con cuatro mesas dentro y unas diez en la arena.
La fritura variada incluye boquerón, calamar, salmonete, chopitos y a veces puntillitas. El aceite es limpio (se nota en que no se queda pegado al plato) y la harina es de garbanzo mezclada con trigo, así que el rebozado no queda plastificado. Una fritura para dos personas ronda los dieciocho euros.
Un truco: pide también unos pimientos fritos verdes pequeños (palos de los buenos) y espolvoréales sal encima. Por dos euros más tienes uno de los mejores complementos que existen para un pescado frito bien hecho.
## La Lonja: la opción cara que a veces vale la pena
Hay un chiringuito que se aleja del modelo tradicional y apuesta por un estilo más restaurante: **La Lonja**, en el puerto deportivo. No es barato. Comer para dos con vino se te puede ir a setenta euros fácilmente. Pero la calidad del producto está al nivel del precio, que no es poco decir.
Su punto fuerte son los mariscos. Las gambas rojas de Málaga cuando las hay, el bogavante a la plancha, las ostras gallegas con una vinagreta de chalota. Es el sitio al que voy cuando hay algo que celebrar, no el sitio de cada semana. Pero cuando toca, no defrauda.
Un consejo: reserva y pide por la mañana qué es lo fresco del día. A veces tienen atún rojo de almadraba, a veces tienen jurel de roca. Si vas sin preguntar te quedas con la carta estándar, que es correcta pero no es lo mejor del sitio.
## Lo que evito

Hay varios tipos de chiringuitos que he aprendido a descartar:
**Los que tienen "paella" en el cartel de la calle.** La paella en un chiringuito de la Costa del Sol es una señal de alarma. Los que saben hacerla bien están en Valencia. Los que te la venden aquí casi siempre la tienen hecha desde hace horas en una paellera gigante y la recalientan. Evita.
**Los que tienen ofertas de "menú turístico 15 euros".** Son menús pensados para gastar lo mínimo por cliente, con producto congelado, raciones pequeñas, y calidad proporcional al precio. Si lo que quieres es un chiringuito, pide a la carta. Si lo que quieres es comer barato, hay mejores opciones en el centro que en la playa.
**Los que venden "cóctel de camarón" o "ensalada de marisco" en la carta.** Ambos platos son un vehículo para usar marisco congelado con mayonesa barata. Ningún chiringuito serio tiene estos platos en temporada; los que los tienen están tirando de despensa vieja.
**Los que ponen música alta en la terraza.** Un chiringuito bueno no necesita música. El sonido del mar y las conversaciones bastan. Cuando tienen altavoces pinchando reggaetón en bucle es porque quieren clientela que no preste atención a la comida.
## La temporada importa
Un aspecto que casi nadie menciona: los chiringuitos no son iguales todo el año.
**De octubre a abril** es mi temporada favorita. Menos gente, producto de temporada (el boquerón de enero es otra cosa), los dueños están accesibles para conversar, puedes comer a las dos y media sin reserva. La pega: algunos cierran entre semana o incluso todo el invierno.
**De mayo a junio** es la mejor combinación: buen tiempo, producto bueno, todavía sin masificación. Si tuviera que recomendar meses concretos para comer en chiringuitos, serían mayo y junio.
**Julio y agosto** son una trampa. Los chiringuitos se llenan de turistas, las colas son eternas, la calidad baja porque no les da tiempo, y los precios suben un 15-20%. Si vas en estos meses, reserva, ve a las tres de la tarde o a las nueve de la noche, y asume que no vas a comer en las mismas condiciones que en mayo.
**Septiembre** recupera. La gente se va, el producto sigue bien, y el agua está a 24 grados. Comer en septiembre en la playa es uno de los placeres más infravalorados del año en la Costa del Sol.
## La pregunta del espeto
Todo chiringuito auténtico debería tener espeto de sardinas, pero no todos lo hacen igual. Tres señales para distinguir el bueno del malo:
**El fuego tiene que ser de olivo**, no carbón. El carbón da un sabor fuerte y homogéneo que mata el matiz de la sardina. El fuego de olivo tiene un aroma más dulce y deja que el pescado sepa a pescado.
**Las cañas tienen que ser de caña verdadera**, no metal. La caña aporta un mínimo de humedad y evita que la sardina se seque. Los espetos de varilla metálica son una comodidad del chiringuito, no una elección gastronómica.
**El espetero tiene que estar delante de la espetera cuando tú llegas.** Si las sardinas las meten en la espetera, las dejan veinte minutos y las sacan cuando te las traen, el resultado es irregular. Un espetero bueno voltea los espetos, los mueve, los retira antes si el fuego está fuerte. Es un trabajo artesanal.
## Mi ruta cuando vienen amigos de fuera
Cuando tengo que impresionar a alguien que viene a visitarnos, la ruta clásica es: caña y ensaladilla en El Cachalote, espetos allí mismo, y si queda hambre, postre en otro sitio porque los postres de los chiringuitos son, sin excepción, mejorables. Un flan industrial o una tarta de queso hecha hace tres días no rescatan un almuerzo bueno. Prefiero levantarme y tomar un café con un dulce en algún bar del paseo, o directamente volver a casa.
Para cenas, La Lonja si es celebración, El Cachalote si es más informal. Para comidas de amigos los sábados, casi siempre El Cachalote o La Cucharita según si vamos a espetos o a fritura.
## Para terminar
Los chiringuitos de Fuengirola siguen siendo una de las cosas que más defiendo cuando alguien me pregunta por qué vivo aquí. No porque sean únicos (los hay en toda la provincia) sino porque son accesibles: son parte de la vida cotidiana, no un evento especial. Un sábado al mediodía, pedir unos espetos con una caña mirando el mar, y volver a casa andando por el paseo, es algo que se puede hacer varias veces al año sin que pierda gracia. Y eso, en una vida adulta donde casi todo se ha vuelto planificado, es un lujo discreto.
---
# Leer en papel en 2026: por qué sigo comprando libros
URL: https://javiervalencia.net/post/leer-en-papel-en-2026
Tengo un Kindle desde 2014. Lo uso, funciona bien, ha sobrevivido a tres viajes largos y un vaso de agua. Y aun así la mayoría de los libros que leo son de papel, los compro nuevos o de segunda mano, los acumulo en estanterías que ya no tienen hueco y los arrastro cada vez que me mudo. A ojos de cualquiera con un mínimo de pragmatismo, esto es absurdo. Pero cuanto más mayor me hago, más claro lo tengo: lo hago a propósito.
## El Kindle es mejor para casi todo

Empecemos por lo obvio. Un Kindle es técnicamente superior al papel en casi cualquier métrica medible. Pesa menos, cabe en el bolsillo, tiene luz propia, te deja subrayar y exportar las notas, el diccionario está a un toque, no se te acaba nunca el libro porque tienes cientos disponibles en segundos. Si lees en el avión, en el metro o en la cama sin despertar a quien duerme a tu lado, un lector electrónico es claramente la opción correcta.
Y sin embargo. Y sin embargo.
Llevo diez años conviviendo con el Kindle y no he conseguido que los libros que he leído en él dejen huella en mi memoria del mismo modo que los libros de papel. No sé si es efecto placebo, si es un sesgo generacional mío, si es algo medible o directamente una invención de los que seguimos comprando libros. Pero es una sensación constante: los libros físicos se me quedan, los digitales se me evaporan.
## El objeto importa
Un libro de papel tiene una presencia física que un fichero EPUB no tiene. Ocupa sitio en una estantería. Tiene un peso concreto cuando lo coges. Huele a algo, a papel nuevo o a polvo. Las páginas se doblan, se manchan, se subrayan. Al cabo de unos años un libro leído muchas veces se parece al que lo ha leído: tiene las esquinas dobladas donde te parabas, la tapa desgastada del lado por donde lo sujetas, notas a lápiz en los márgenes de los capítulos que te importaban.
Esto no es nostalgia vacía. Es memoria encarnada. Cuando vuelvo a coger un libro que leí hace años, el objeto me devuelve cosas que el contenido solo no me devolvería. Recuerdo dónde estaba cuando lo leí, en qué época de mi vida fue, por qué lo había comprado. El libro funciona como un marcador temporal. Un fichero en una nube no hace eso.
## La atención es otra

Leer en un dispositivo conectado a internet es leer con una puerta entreabierta. Por mucho que pongas el Kindle en modo avión o que uses un lector dedicado sin navegador, tu cerebro sabe que estás en un aparato. Y los aparatos, en 2026, son por defecto un portal a notificaciones, mensajes, interrupciones, otra cosa que hacer.
Con un libro de papel no hay esa puerta. Estás tú, el texto y una taza de café. Si te cansas, levantas la vista. Si te distraes, te distraes con la pared, no con un banner. Esa ausencia de potencial interrupción cambia la calidad de la lectura. Lees más despacio, te vas más atrás cuando no has entendido algo, te permites pararte a pensar.
Me he dado cuenta de que cuando leo en Kindle tengo la tentación inconsciente de "avanzar", como si el ritmo de una app lo hubiera importado a la lectura. Con un libro de papel me puedo quedar media hora pensando en un párrafo y no me siento culpable por no estar "progresando".
## El dinero también cuenta
Un libro nuevo cuesta entre quince y veinticinco euros. Un libro de segunda mano, cinco o diez. Leer en digital pirateado es gratis. Leer con una suscripción a Scribd o similar son diez euros al mes.
Por coste puro, el papel pierde. Pero hay un detalle que cambia el cálculo: cuando pago por un libro, lo leo. Cuando descargo cincuenta libros gratis, leo cero. La fricción de pagar quince euros por un objeto físico es, paradójicamente, lo que me asegura que voy a abrirlo. Lo he comprado, lo tengo delante, me mira desde la mesita. Los libros en PDF que "alguna vez" iba a leer siguen en la carpeta donde los dejé hace cinco años.
Hay un concepto en psicología conductual que explica esto: el coste hundido activa el compromiso. No es racional, pero funciona. Y la lectura, para alguien que no se dedica a ello profesionalmente, necesita todos los empujones que pueda tener.
## Las bibliotecas personales dicen algo

Mi biblioteca es pequeña comparada con la de mucha gente que conozco. Tendré unos trescientos libros, entre los que tengo aquí y los que he ido dejando en casa de mis padres. No es una colección espectacular. Pero es mía.
Cuando alguien entra en mi casa por primera vez y pasa cerca de la estantería, casi siempre se para a mirar. A veces comentan algo, a veces no. Pero la conversación que sigue es distinta de la que habría sido sin los libros. Los libros físicos son uno de los pocos objetos que ofrecen información sobre quién eres sin tener que contarla.
En digital esto no existe. Nadie te va a preguntar por los libros de tu Kindle. Nadie los va a ver nunca. Lo que lees en digital es literalmente privado, y no siempre es bueno que lo sea. La conversación sobre libros, que para mí es una de las grandes alegrías de la vida adulta, depende de que los libros sean visibles.
## Lo que me pasa en las librerías
Una de las razones por las que sigo comprando libros en tiendas físicas es algo que no pasa en Amazon: entrar con la idea de comprar un libro y salir con tres que no sabía que existían. El algoritmo no me recomienda mal, pero me recomienda mal *para mí*. Me recomienda lo que ya iba a comprar, lo que me conduce a lo que ya me interesa. Una librería bien curada, con un librero que sabe lo que tiene, te lleva a donde no sabías que querías ir.
He descubierto la mitad de mis autores favoritos por tropezarme con ellos en una mesa de novedades o en una estantería sin sentido aparente. Nunca por recomendación automatizada. Las librerías son, en ese sentido, un servicio que el comercio electrónico no ha replicado y probablemente no pueda replicar: serendipia informada.
## Prestar y heredar
Un libro de papel se presta. Un libro digital, en la práctica, no. Los sistemas de préstamo de libros electrónicos existen pero son una pesadilla burocrática, están llenos de restricciones y nadie los usa realmente. Un libro de papel se lo das a un amigo y punto. Si te lo devuelve, bien; si no, también. El libro viaja y con él viaja algo.
Lo mismo con heredar. Mis libros favoritos probablemente acaben con mis hijas si las interesa, o en cajas donadas a una biblioteca pública si no. Tendrán una segunda vida. Mi colección del Kindle va a desaparecer el día que Amazon decida cerrar la cuenta o cambiar las condiciones. Es un activo ilusorio.
## Lo que no defiendo
Para que quede claro: no estoy diciendo que leer en digital sea peor ni que quien lea en Kindle lea menos o peor. Conozco gente que devora libros en su lector electrónico y no le cambiaría el hábito por nada. Lo que describo es mi experiencia, mis hábitos y lo que a mí me funciona.
Tampoco estoy en contra de la tecnología. El Kindle sigue siendo la mejor opción para viajar largo, para probar autores nuevos baratos, para libros técnicos que consultas por búsqueda y no por lectura lineal. Hay casos donde el papel es peor.
## Lo que sí defiendo
Defiendo que el formato importa. Que no todos los soportes son iguales. Que elegir a propósito cómo lees, dónde lees y con qué lees es una decisión que afecta a lo que sacas de la lectura. Y que, al menos para mí, seguir comprando libros de papel no es un capricho nostálgico: es una forma de reservar un espacio donde la atención no está mediada por un dispositivo, donde el objeto refuerza el hábito y donde la biblioteca es a la vez un reflejo y un motor de lo que quiero ser.
En 2026, con todas las alternativas digitales disponibles, leer en papel es una elección. Y creo que esa elección, como otras pequeñas, dice bastante de cómo quieres que sea tu vida.
---
# Backups con restic: sencillo, cifrado y automatizado
URL: https://javiervalencia.net/post/backups-con-restic
Los backups son como las copias de seguridad dentales: todo el mundo sabe que debería hacerlos con frecuencia, casi nadie los hace bien, y cuando los necesitas, el que no los tiene paga carísimo. He tenido suficientes sustos en veinte años como para haber convertido los backups en una parte no negociable de cualquier servidor que me importe. Y desde hace cuatro años, mi herramienta para todo es **restic**. Este post cuenta por qué y cómo la uso.
## Qué es restic

Restic es un programa de backups escrito en Go, de código abierto, que hace cuatro cosas fundamentales bien:
1. **Snapshots incrementales con deduplicación**. Cada backup guarda solo lo que ha cambiado desde el anterior. Si un fichero no cambia, no ocupa espacio adicional. Si dos ficheros son idénticos (en distintas carpetas o en distintos días), se guarda solo una copia.
2. **Cifrado de extremo a extremo**. Los datos se cifran antes de salir de tu máquina. El repositorio puede vivir en un servicio público (S3, Backblaze B2, Google Drive) y el proveedor no puede ver nada de tu contenido.
3. **Restauración granular**. Puedes restaurar un fichero concreto de un snapshot concreto sin restaurar todo. Muy útil cuando lo que necesitas es solo una carpeta.
4. **Multiplataforma**. Funciona igual en Linux, macOS y Windows. Los repositorios son portátiles entre sistemas.
Restic no es la única herramienta que hace esto (están `borg`, `duplicity`, `kopia`). Elegí restic por la simplicidad de la interfaz y por el soporte de backends (S3, B2, SFTP, local, y más). Pero cualquiera de las tres es decente.
## El modelo conceptual
Entender restic es entender tres cosas:
**Repositorio**: el sitio donde guardas los backups. Puede ser un directorio local, un SFTP, un bucket S3, un Backblaze B2. Está cifrado con una contraseña que solo tú conoces.
**Snapshot**: una foto del estado de los ficheros en un momento dado. Cada `restic backup` crea un snapshot nuevo.
**Política de retención**: cuántos snapshots guardas y cuáles tiras. Esto se hace con `restic forget` y `restic prune`.
Con estos tres conceptos ya puedes usar el 95% de restic.
## Instalación

En Debian/Ubuntu moderno, restic está en los repositorios:
```bash
sudo apt install restic
```
Si la versión del repo es vieja (revisa con `restic version`), descarga el binario directamente:
```bash
wget https://github.com/restic/restic/releases/download/v0.17.0/restic_0.17.0_linux_amd64.bz2
bunzip2 restic_0.17.0_linux_amd64.bz2
sudo mv restic_0.17.0_linux_amd64 /usr/local/bin/restic
sudo chmod +x /usr/local/bin/restic
```
Con eso ya tienes restic funcionando.
## Inicialización del repositorio
Lo primero es crear un repositorio. En mi caso uso Backblaze B2 porque es barato (seis dólares al mes por un terabyte), con amplia disponibilidad y con soporte nativo en restic.
```bash
export RESTIC_REPOSITORY="b2:mi-bucket:backups/servidor"
export B2_ACCOUNT_ID="xxxxxxxx"
export B2_ACCOUNT_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxx"
export RESTIC_PASSWORD="una-contraseña-fuerte-y-larga"
restic init
```
**Guarda la `RESTIC_PASSWORD` en un sitio seguro** (password manager, papel en caja fuerte). Si la pierdes, los backups son inútiles: no hay backdoor. Esto es una feature, no un bug, pero exige disciplina.
Para un repositorio local:
```bash
export RESTIC_REPOSITORY="/mnt/backup-drive/restic"
export RESTIC_PASSWORD="contraseña"
restic init
```
Para SFTP a otro servidor:
```bash
export RESTIC_REPOSITORY="sftp:usuario@servidor.com:/backups/restic"
export RESTIC_PASSWORD="contraseña"
restic init
```
## El primer backup

La sintaxis básica:
```bash
restic backup /home /etc /var/www
```
En mi servidor principal el comando es:
```bash
restic backup \
--exclude-file=/etc/restic/excludes.txt \
--tag nightly \
/home /etc /var/www /opt/app
```
`--exclude-file` apunta a una lista de patrones a ignorar. Mi fichero típico:
```
node_modules
*.log
*.tmp
/var/cache
__pycache__
.git
.DS_Store
```
`--tag` le pone una etiqueta al snapshot para poder filtrar después.
La primera ejecución tarda (sube todos los datos). Las siguientes son rápidas porque solo suben lo que cambió.
## Automatización con systemd
Un cron funciona, pero prefiero systemd timers por tres razones: logs integrados, `Persistent=true` (si el servidor estaba apagado, ejecuta al encender) y no tengo que lidiar con rutas absolutas y variables de entorno.
El unit file (`/etc/systemd/system/restic-backup.service`):
```ini
[Unit]
Description=Backup con restic
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
ExecStart=/usr/local/bin/restic backup \
--exclude-file=/etc/restic/excludes.txt \
--tag nightly \
/home /etc /var/www /opt/app
ExecStartPost=/usr/local/bin/restic forget --prune \
--keep-daily 7 --keep-weekly 4 --keep-monthly 12
```
El timer (`/etc/systemd/system/restic-backup.timer`):
```ini
[Unit]
Description=Backup nocturno con restic
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.target
```
El fichero de entorno (`/etc/restic/env`, modo 600, propietario root):
```
RESTIC_REPOSITORY=b2:mi-bucket:backups/servidor
B2_ACCOUNT_ID=xxxxxxxx
B2_ACCOUNT_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxx
RESTIC_PASSWORD=contraseña-fuerte
```
Activar:
```bash
sudo systemctl enable --now restic-backup.timer
sudo systemctl list-timers restic-backup.timer
```
## Política de retención
El `ExecStartPost` de arriba tiene la política que uso:
- `--keep-daily 7`: los últimos 7 backups diarios.
- `--keep-weekly 4`: los últimos 4 backups semanales.
- `--keep-monthly 12`: los últimos 12 backups mensuales.
Con esta política siempre tengo: la última semana día a día, el último mes semana a semana, y el último año mes a mes. Es suficiente para la mayoría de casos. Si necesito más granularidad en ciertas fechas (por ejemplo, guardar un backup antes de una migración), creo un snapshot manual con `--tag manual` y luego uso `--keep-tag manual` en la política para que nunca se borre.
El `--prune` al final recupera el espacio físico de los snapshots eliminados. Si no lo pones, los snapshots se marcan como eliminados pero el espacio no se libera.
## Cómo restauro
La operación que justifica todo el sistema. Primero listo snapshots:
```bash
restic snapshots
```
Output:
```
ID Time Host Tags Paths
----------------------------------------------------------------
abcd1234 2026-04-01 03:30:00 servidor nightly /home /etc
efgh5678 2026-04-02 03:30:00 servidor nightly /home /etc
ijkl9012 2026-04-03 03:30:00 servidor nightly /home /etc
```
Para restaurar un snapshot completo a un directorio:
```bash
restic restore abcd1234 --target /tmp/restore
```
Para restaurar solo un fichero o carpeta:
```bash
restic restore abcd1234 --target /tmp/restore --include /home/javier/proyectos
```
Para montar un snapshot como filesystem y navegarlo (muy útil):
```bash
mkdir /mnt/restic
restic mount /mnt/restic
```
Y ahora `/mnt/restic/snapshots/` tiene todos los snapshots accesibles como carpetas. Puedes hacer `cp`, `diff`, lo que sea. Cuando terminas, `Ctrl+C` para desmontar.
## Verificación
Un backup que no has verificado es un backup que quizás no funciona. Yo ejecuto `restic check` mensualmente:
```bash
restic check --read-data-subset=10%
```
`--read-data-subset=10%` lee el 10% de los datos reales y verifica que no hay corrupción. Hacer el check completo sobre un terabyte es carísimo (tienes que descargarlo todo); la muestra estadística del 10% da una señal fiable con coste razonable.
Además, cada seis meses hago una **restauración de prueba**: bajo un snapshot a una máquina de pruebas y verifico que el contenido es usable. Si los ficheros están cifrados o corruptos, quiero saberlo con seis meses de margen, no el día que lo necesito de verdad.
## Costes reales
En mi caso concreto, servidor con unos 80 GB de datos reales:
- Backblaze B2: $0.48/mes en almacenamiento, prácticamente cero en bandwidth (los uploads son gratis, los downloads son $0.01/GB pero raramente descargo nada).
- Tiempo de backup diario: unos 3 minutos.
- Tiempo de restore completo (si alguna vez hiciera falta): alrededor de dos horas.
Total: menos de seis euros al año por tener todo mi trabajo protegido. Es uno de los mejores ratios coste/valor que conozco en infraestructura.
## Errores comunes
Cosas que he visto fallar en setups de backup:
**No cifrar los backups.** Los backups son un objetivo jugoso para un atacante: tienen toda tu información ordenada. Sin cifrado, expones todo. Restic cifra por defecto; no lo desactives.
**No probar las restauraciones.** Muchos sistemas tienen backups que nadie ha restaurado nunca. La primera restauración no puede ser el día del desastre. Haz una de prueba cada seis meses.
**Ubicar el backup en la misma infraestructura.** Un snapshot de LVM no es un backup. Un backup en el mismo datacenter no es un backup. El backup tiene que vivir físicamente lejos de los datos originales.
**No versionar la configuración del backup.** El unit file, los excludes, el script de restauración… todo eso tiene que estar en un sitio versionado y accesible. Si tu máquina arde entera, el backup es inútil si no recuerdas cómo se configuraba.
**Olvidarse de la contraseña.** El cifrado sin recuperación es un arma de doble filo. La contraseña en un gestor de contraseñas, y el gestor con su propia recuperación. Sin esto, un incidente grave puede dejarte sin acceso a tus propios backups.
## Cuándo restic no es la respuesta
Restic es excelente para backup de ficheros en servidores y máquinas personales. Pero tiene escenarios donde no encaja:
- **Backup de bases de datos en caliente**: restic hace snapshot a nivel de filesystem; para una base de datos en uso, mejor hacer `pg_dump` o `mysqldump` primero y después hacer backup del dump.
- **Réplica en tiempo real**: restic es snapshots periódicos, no replicación continua. Si necesitas pérdida de datos cero, necesitas replicación (PostgreSQL streaming, rsync con inotify).
- **Archivado legal con retención larga**: restic guarda snapshots, pero si tienes requisitos legales específicos (GDPR, HIPAA), valida que la retención cumple los requisitos antes de producción.
## La conclusión
Montar restic en una tarde y saber que los datos de tu servidor están a salvo es uno de esos cambios pequeños con impacto desproporcionado. La diferencia entre "sé que tengo backup" y "creo que tengo backup" es la diferencia entre dormir tranquilo y no dormir el día que algo se jode. Seis euros al año por esa tranquilidad es un lujo.
---
# Git aliases que uso cada día
URL: https://javiervalencia.net/post/git-aliases-que-uso-cada-dia
Git es una herramienta enorme. La mayoría de desarrolladores conoce bien veinte comandos, apenas usa cinco y escribe los cinco sin parar durante ocho horas al día. Cuando escribes `git status` cien veces por día y `git checkout -b` otras treinta, cada carácter ahorrado empieza a sumar. Y más allá del ahorro de teclas, un buen conjunto de aliases te convierte ciertos comandos incómodos en comandos rápidos, y te evita los sustos que suelen venir de teclear mal algo con `--force`.
Este post es mi `.gitconfig` comentado, después de diez años afinándolo. No es exhaustivo: es el mínimo que me llevaría a cualquier máquina nueva.
## Dónde van los aliases

Los aliases de git viven en el fichero `~/.gitconfig` bajo la sección `[alias]`:
```ini
[alias]
st = status
co = checkout
br = branch
```
Puedes añadirlos con `git config --global alias.st status` o editando el fichero a mano. Yo los edito a mano porque la mayoría son multilínea y el comando `git config` es incómodo para eso.
Los aliases que empiezan con `!` son comandos shell arbitrarios, no solo subcomandos de git. Esto abre la puerta a muchísimas cosas, como veremos más adelante.
## Los básicos (y por qué sí me molesto en tenerlos)
Empecemos por los aliases que son solo abreviaturas:
```ini
[alias]
st = status
co = checkout
br = branch
ci = commit
sw = switch
cp = cherry-pick
```
Parecen una tontería, pero estos seis aliases me ahorran, literalmente, horas al año. `git st` en vez de `git status` son cinco caracteres menos. Multiplicado por cien veces al día, por doscientos días al año, por diez años. Más lo que gano en momentum mental: no tengo que pararme a teclear una palabra larga cuando el pensamiento ya ha pasado a otra cosa.
Nota sobre `sw` y `co`: `switch` es el comando moderno para cambiar de rama (Git 2.23+). Lo prefiero a `checkout` cuando solo voy a cambiar de rama, porque `checkout` hace demasiadas cosas distintas y es fácil meter la pata. Para operaciones sobre ficheros sigo usando `checkout` o `restore`.
## Log decente

El log por defecto de git es feo y poco útil. Este alias es probablemente el que más uso en la vida:
```ini
[alias]
lg = log --graph --pretty=format:'%C(yellow)%h%Creset -%C(red)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
```
Resultado: un árbol de commits con hash, referencias, mensaje, fecha relativa y autor, todo en una línea por commit. Cabe más historial en la pantalla y la estructura de ramas se ve de un vistazo.
Si el gráfico es demasiado para algún caso concreto:
```ini
[alias]
lgo = log --oneline --decorate
```
Y para ver un log más rico cuando quiero investigar algo:
```ini
[alias]
lgf = log --graph --pretty=fuller
```
## Diff y show útiles
Los diffs por defecto son correctos pero hay variantes que uso mucho:
```ini
[alias]
dc = diff --cached
ds = diff --stat
dw = diff --word-diff
```
`dc` es el diff de lo que está staged, lo opuesto al diff por defecto. Cuando estás a punto de hacer commit, `git dc` te muestra exactamente lo que vas a committear.
`ds` te da un resumen de ficheros modificados con número de líneas añadidas/borradas. Útil cuando el diff es muy grande y quieres primero un mapa.
`dw` hace word-diff en vez de line-diff. Esencial para ver qué cambió en un fichero largo donde solo han cambiado palabras sueltas (documentación, config files).
## Commits rápidos y commits de emergencia

Algunos aliases que uso casi sin pensar:
```ini
[alias]
cm = commit -m
ca = commit -a -m
amend = commit --amend --no-edit
fixup = commit --fixup
wip = commit -am "WIP"
```
`cm` es para commits con mensaje inline. `ca` añade todo lo modificado y hace commit en una línea. `amend` rehace el último commit sin cambiar el mensaje (útil cuando te has dejado algo fuera). `fixup` crea un commit marcado como fixup de otro, para luego hacer `rebase --autosquash`.
`wip` es el que uso cuando necesito cambiar de rama y no quiero pararme a pensar en el mensaje. Comiteo con "WIP", cambio de contexto, y cuando vuelvo hago `git reset HEAD~1` y retomo.
## Push y pull con menos tecla
```ini
[alias]
ps = push
psu = push -u origin HEAD
pl = pull --rebase
```
`psu` es el que uso cuando empujo una rama nueva por primera vez: empuja HEAD a una rama con el mismo nombre en origin y establece el tracking. Me ahorro `git push -u origin nombre-de-la-rama` con riesgo de errata.
`pl` siempre con `--rebase` porque prefiero un historial lineal a un merge commit cada vez que tiro del repo.
## Deshacer sin romper nada
Los aliases para deshacer son los que más miedo me han ahorrado:
```ini
[alias]
undo = reset HEAD~1 --mixed
discard = checkout --
unstage = reset HEAD --
```
`undo` deshace el último commit pero conserva los cambios como modificaciones. Si el commit era una mierda, editas y vuelves a committear.
`discard` descarta cambios no committeados en un fichero. `git discard src/main.go` y el fichero vuelve a como estaba en el último commit.
`unstage` saca del index cosas que habías añadido con `add` pero que no quieres committear todavía.
Estos tres aliases son el equivalente a "deshacer" en una interfaz gráfica. Son los que más suelo recomendar a gente que empieza con git.
## Ramas: limpiar y moverse
Estos son los que salvan la vida después de unas semanas de trabajo en un repo:
```ini
[alias]
cleanup = "!git branch --merged main | grep -v '^\\*\\|main\\|master' | xargs -n 1 git branch -d"
latest = "!git for-each-ref --sort=-committerdate refs/heads/ --format='%(refname:short)' | head -20"
current = rev-parse --abbrev-ref HEAD
```
`cleanup` borra todas las ramas locales que ya están fusionadas a main. En un repo de trabajo acumulas fácilmente cincuenta ramas cerradas; esto las limpia en un comando.
`latest` te muestra las veinte ramas locales ordenadas por último commit. Perfecto para "¿cómo se llamaba la rama aquella de hace tres días?".
`current` escupe el nombre de la rama actual. Lo uso en scripts más que interactivamente.
## Rebase sin angustia
El rebase interactivo es donde más gente se queda atascada:
```ini
[alias]
ri = rebase -i
rim = "!git rebase -i $(git merge-base HEAD main)"
continue = rebase --continue
abort = rebase --abort
```
`rim` es el que uso más: hace rebase interactivo desde el punto de divergencia con main. No tengo que acordarme de qué commit es la base; git lo calcula solo.
`continue` y `abort` están porque durante un rebase conflictivo escribo `git continue` diez veces. Mejor un alias directo.
## Exploración de código
Estos son los que uso para navegar por repos que no conozco:
```ini
[alias]
who = shortlog -sn --
blame-line = "!f() { git log -L $1,$1:$2; }; f"
recent = "!git log --since='2 weeks ago' --pretty=format:'%h %s (%an, %cr)'"
contributors = shortlog -sn --no-merges
```
`who` te dice quién ha committeado más veces en un fichero. Útil para saber a quién preguntar.
`blame-line` te da la historia completa de una línea concreta de un fichero. `git blame-line 42 src/main.go` te muestra todos los commits que tocaron la línea 42 de ese fichero, no solo el último.
`recent` es un log filtrado por "las últimas dos semanas". Perfecto cuando vuelves de vacaciones y necesitas ponerte al día.
## Stash: guardar y recuperar
```ini
[alias]
ss = stash save
sp = stash pop
sl = stash list
sd = stash drop
```
No hay mucho que explicar aquí: son las operaciones de stash abreviadas. El stash es una herramienta infrautilizada; con aliases cortos la uso mucho más.
## El alias que me salvó varios días
Uno específico que merece mención aparte:
```ini
[alias]
saveme = "!git stash && git checkout main && git pull && git checkout - && git stash pop"
```
Traducción: guarda cambios, cambia a main, actualiza, vuelve a tu rama, recupera cambios. Lo uso cuando estoy a medio código y necesito una versión actualizada de algo en main sin perder lo que tengo hecho. Escribir esa secuencia a mano es un foco de errores; un alias la hace atómica.
## Aliases que no recomiendo
He visto a gente tener aliases que son malas ideas:
**Aliases para `push --force`**. El force push es peligroso: puede borrar trabajo de otros en ramas compartidas. Si vas a hacer force push, quiero que cueste teclearlo. Nunca lo abrevio.
**Aliases para `reset --hard`**. Igual. Si vas a destruir cambios, quiero que sean dos palabras completas, no un atajo. La fricción es una feature, no un bug.
**Aliases con secretos hardcodeados**. Los aliases viven en `.gitconfig`, que a veces está en repos (dotfiles). No metas ahí tokens, credenciales ni nada que no puedas compartir.
## Mi fichero completo
El `.gitconfig` que llevo a cada máquina cabe en unas cuarenta líneas. Lo tengo en mi repo de dotfiles y lo clono con el resto de mi setup cuando monto un entorno nuevo. No es una gran inversión de tiempo y el ROI es enorme: una vez que tu muscle memory tiene `git st`, `git lg` y `git psu`, no vuelves atrás.
## La recomendación real
Si estás pensando "voy a copiar todos estos aliases de golpe", no. Hazlo al revés: añade uno cada vez que notes que estás escribiendo el mismo comando largo por tercera vez. Así, en tres meses, tendrás una colección personalizada a tu forma de trabajar, y cada alias te va a importar. Copiar treinta aliases de una lista es ruido; descubrirlos por necesidad propia es productividad real.
---
# Conducir una Honda Forza 125 por la Costa del Sol
URL: https://javiervalencia.net/post/conducir-una-honda-forza-125-por-la-costa-del-sol
Tengo una Honda Forza 125. No es una moto de verdad si le preguntas a un motero, que te mirará con condescendencia y te explicará que ciento veinticinco centímetros cúbicos no son suficientes para nada. Tiene razón en los números y se equivoca en todo lo demás.
## Por qué un scooter

La respuesta corta: aparcamiento. Vivo en la Costa del Sol, donde aparcar en verano es un deporte extremo y en invierno es solo difícil. Con el coche puedes tirarte veinte minutos buscando sitio. Con la Forza llegas, apartas las cuatro macetas del aparcamiento de motos que está justo delante de donde vas, y ya está. Esa diferencia de veinte minutos, multiplicada por todas las veces que sales al día, es tiempo de vida que recuperas.
La respuesta larga: libertad. No hay otra palabra. Un scooter en una zona como la Costa del Sol, donde la temperatura permite rodar prácticamente todo el año, te da una libertad de movimiento que el coche no te da. No piensas en si habrá sitio para aparcar. No piensas en el tráfico porque te filtras entre los coches en los atascos. No piensas en la gasolina porque con tres euros llenas el depósito y haces doscientos kilómetros.
## La Forza en concreto
Honda hace scooters como hace todo lo demás: bien, sin drama y sin que se rompa nada. La Forza 125 es un scooter de rueda alta con un motor monocilíndrico que da doce caballos, que no es mucho pero es exactamente lo que necesitas para moverte por ciudad y carretera comarcal.
Lo mejor de la Forza es lo que no notas: que funciona. Arranca siempre. Frena cuando tiene que frenar. La suspensión absorbe los baches de las carreteras de la costa, que son muchos y variados. El asiento es cómodo para una hora de viaje sin que se te duerma nada. El hueco del asiento cabe un casco integral. El consumo es ridículo: dos litros a los cien.
Lo peor es el viento. Al no tener carenado completo, a partir de ochenta kilómetros por hora el viento te empuja. No es peligroso pero es incómodo. La pantalla de serie ayuda pero no hace milagros. He pensado en ponerle una pantalla más alta pero entonces deja de quedar bonita, y seamos honestos: la estética importa.
## Las rutas

Hay tres rutas que hago regularmente y cada una tiene su personalidad.
La primera es la costera: de Mijas Costa a Fuengirola y vuelta. Quince minutos por la carretera de la costa, con el mar a la izquierda, pasando por La Cala, por el puerto deportivo y por el centro de Fuengirola. Es la ruta utilitaria, la que hago para ir a comprar algo o para tomar un café. Nada espectacular pero el mar siempre está ahí y nunca aburre.
La segunda es la de montaña: subir a Mijas Pueblo por la carretera que serpentea desde la costa. Son diez minutos de curvas con vistas que quitan el hipo. Subes quinientos metros de altitud en unos pocos kilómetros y cuando llegas arriba ves toda la costa desde Málaga hasta Marbella. En la Forza las curvas se hacen bien porque es ágil y ligera, aunque en las rampas más pronunciadas notas que ciento veinticinco centímetros cúbicos son ciento veinticinco centímetros cúbicos.
La tercera es la de domingo: bajar hasta Marbella por la antigua carretera nacional, evitando la autopista. Son cuarenta minutos de curvas suaves, pasando por Calahonda, Cabopino y los campos de golf. Esta ruta es la que hago cuando necesito desconectar de verdad. Cuarenta minutos conduciendo sin pensar en nada más que en la carretera, el motor y la siguiente curva.
## Los días de lluvia
En la Costa del Sol llueve poco pero cuando llueve, llueve de verdad. Y conducir un scooter bajo la lluvia es una experiencia que te hace replantear todas las decisiones que te han llevado hasta ese momento.
El problema no es mojarte, que también. El problema es que las carreteras de aquí, que llevan meses sin ver agua, se convierten en pistas de patinaje con las primeras gotas. La grasa acumulada, el polvo, los restos de aceite, todo eso forma una capa sobre el asfalto que con la lluvia se vuelve más resbaladiza que una pista de hielo. La primera media hora de lluvia es la peor. Después, cuando el agua ha lavado la carretera, mejora. Pero esa primera media hora la pasas con el corazón en la boca.
Mi estrategia para la lluvia es sencilla: no salgo. Suena cobarde pero es sentido común. Si miro por la ventana y está lloviendo, cojo el coche. No hay prisa que justifique jugarse el cuello en un scooter sobre asfalto mojado. La Forza se queda en el garaje y yo mantengo los huesos intactos.
## La comunidad invisible

Cuando conduces un scooter descubres que hay una comunidad invisible de gente que se mueve sobre dos ruedas. Nos reconocemos. Nos saludamos con un gesto de cabeza o levantando dos dedos de la mano izquierda. No nos conocemos de nada pero compartimos algo: la decisión de no ir en coche cuando el coche es la opción obvia.
Hay una jerarquía no escrita. Los de moto grande miran a los de scooter con cierta superioridad. Los de Vespa se consideran más elegantes que los de scooter japonés. Los de scooter japonés nos consideramos más prácticos que todos los demás. Y los ciclistas nos miran a todos con superioridad moral porque ellos no contaminan.
Pero cuando llueve solo quedamos nosotros y los ciclistas. Y los ciclistas se mojan más.
## Ciento veinticinco son suficientes
Mucha gente me pregunta si no me quedo corto con ciento veinticinco centímetros cúbicos. La respuesta es no, para lo que yo hago. No voy por autopista a ciento cuarenta. No hago viajes de quinientos kilómetros. No necesito adelantar camiones en cuestas. Me muevo por la costa, subo al pueblo, bajo a Marbella y vuelvo a casa. Para eso, ciento veinticinco sobran.
Hay una tendencia a pensar que más es siempre mejor. Más cilindrada, más potencia, más velocidad. Pero más también es más peso, más consumo, más seguro, más mantenimiento y, sobre todo, más tentación de hacer cosas que no deberías hacer en una carretera pública.
La Forza 125 es exactamente lo que necesito: un vehículo que me lleva donde quiero ir de forma eficiente, económica y divertida. No me sobra nada y no me falta nada. Y en un mundo que insiste en que siempre necesitas más, hay algo muy satisfactorio en tener justo lo suficiente.
---
# ClickHouse desde cero (I): instalación y primeros pasos
URL: https://javiervalencia.net/post/clickhouse-desde-cero-i-instalacion-y-primeros-pasos
*Primera entrega de la serie **[ClickHouse desde cero a pro](/search?tag=clickhouse-desde-cero)**. Tiempo de lectura estimado: 9 minutos.*
Arrancamos una serie de cinco posts sobre ClickHouse. La idea es ir de no haber tocado nunca ClickHouse a ser capaz de diseñar, optimizar y operar un cluster en producción. Nada de teoría de relleno: lo que se usa el día que tu empresa te dice "oye, tenemos 500 millones de CDRs mensuales, ¿montamos analytics?".
Si todavía tienes dudas sobre cuándo tiene sentido usar ClickHouse frente a PostgreSQL, el post [ClickHouse para desarrolladores que vienen de PostgreSQL](/post/clickhouse-para-desarrolladores-que-vienen-de-postgresql) es una buena introducción previa. En esta serie doy por supuesto que ya has decidido que lo necesitas.
## Qué es ClickHouse, en una frase
ClickHouse es una base de datos **columnar**, **distribuida** y **orientada a analítica** que habla un dialecto propio de SQL. Fue creada en Yandex para procesar los logs de Yandex.Metrica (el equivalente ruso de Google Analytics) y hoy se usa en empresas como Cloudflare, Uber, eBay o GitLab para analítica en tiempo real sobre volúmenes de cientos de miles de millones de filas.
Lo que la hace especial:
- **Compresión brutal**: ratios de 10:1 o 20:1 son normales.
- **Velocidad de escaneo**: cientos de millones de filas por segundo por core.
- **Escalado horizontal**: sharding y replicación incorporados.
- **SQL familiar**: si sabes SQL, puedes empezar el mismo día.
No está diseñada para transacciones, ni para UPDATE/DELETE frecuentes, ni para consultas por clave primaria al estilo OLTP. Eso sigue siendo trabajo de PostgreSQL o MySQL.
## Opciones de instalación
Hay tres caminos razonables para empezar:
1. **Paquetes `.deb` / `.rpm`** sobre un servidor Linux. La opción clásica para producción.
2. **Docker**. La más rápida para probar en local.
3. **ClickHouse Cloud**. El servicio gestionado oficial, con free tier.
Para esta serie vamos a usar Docker porque es reproducible y no mete ficheros en tu sistema. Todo lo que aprendas aplica igual a una instalación nativa.
### Arrancar un ClickHouse local con Docker
```bash
docker run -d \
--name clickhouse \
-p 8123:8123 \
-p 9000:9000 \
-v clickhouse-data:/var/lib/clickhouse \
--ulimit nofile=262144:262144 \
clickhouse/clickhouse-server:latest
```
Algunos detalles importantes:
- El puerto **8123** es el interfaz HTTP (útil para cURL, Grafana, drivers).
- El puerto **9000** es el protocolo nativo (TCP binario, mucho más rápido).
- El volumen `clickhouse-data` persiste los datos entre reinicios.
- El `ulimit` es una recomendación oficial: ClickHouse abre muchos ficheros cuando los *parts* se multiplican.
Comprueba que arranca:
```bash
curl http://localhost:8123/ping
# Ok.
```
Y entra al cliente interactivo:
```bash
docker exec -it clickhouse clickhouse-client
```
### Instalación nativa en Debian/Ubuntu
Para referencia, si prefieres paquetes:
```bash
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg
curl -fsSL 'https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/clickhouse-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/clickhouse-keyring.gpg] \
https://packages.clickhouse.com/deb stable main" \
| sudo tee /etc/apt/sources.list.d/clickhouse.list
sudo apt-get update
sudo apt-get install -y clickhouse-server clickhouse-client
sudo systemctl enable --now clickhouse-server
```
Configuración en `/etc/clickhouse-server/`, datos en `/var/lib/clickhouse/`, logs en `/var/log/clickhouse-server/`.
## El cliente de consola
`clickhouse-client` es la herramienta con la que vas a pasar el 80% del tiempo mientras aprendes. Es mucho más cómodo que `psql`: soporta multilínea nativa, autocompletado, formatos de salida, y puede ejecutar consultas desde ficheros directamente.
Dentro del cliente, estos comandos son los que más vas a usar:
```sql
SHOW DATABASES;
SHOW TABLES FROM system;
DESCRIBE TABLE system.numbers;
```
La base de datos `system` es un pequeño tesoro: contiene metadatos sobre tablas, consultas en curso, métricas internas, merges en marcha, procesos... volveremos a ella a menudo en la [entrega V](/post/clickhouse-desde-cero-v-produccion-replicacion-y-clusters).
Formatos de salida útiles al hacer consultas one-shot:
```bash
clickhouse-client --query "SELECT count() FROM system.numbers LIMIT 1000000" \
--format PrettyCompact
clickhouse-client --query "SELECT number FROM system.numbers LIMIT 5" \
--format JSONEachRow
```
`PrettyCompact`, `TabSeparated`, `CSV`, `CSVWithNames`, `JSONEachRow`, `Parquet`... ClickHouse soporta más de 70 formatos de entrada y salida. Esto es más útil de lo que parece: te permite consumir ficheros externos sin herramientas intermedias.
## Tu primera tabla
Vamos con un ejemplo realista: una tabla de eventos de tráfico web.
```sql
CREATE DATABASE blog;
CREATE TABLE blog.pageviews (
timestamp DateTime,
user_id UInt64,
path String,
referrer String,
country LowCardinality(String),
device LowCardinality(String),
duration_ms UInt32
)
ENGINE = MergeTree()
ORDER BY (timestamp, user_id);
```
Tres piezas importantes que vamos a diseccionar en la [entrega II](/post/clickhouse-desde-cero-ii-tipos-de-datos-y-mergetree):
- **`ENGINE = MergeTree()`**: el motor de tabla. MergeTree es el que vas a usar el 95% del tiempo. Define cómo se almacenan los datos en disco.
- **`ORDER BY`**: la decisión de diseño más importante que vas a tomar. Determina el orden físico de los datos y qué consultas serán rápidas.
- **`LowCardinality(String)`**: tipo optimizado para strings con pocos valores distintos (países, dispositivos, estados). Comprime mejor y escanea más rápido.
## Tu primer INSERT
ClickHouse prefiere inserts en **batch grande**, no fila a fila. Un antipatrón clásico es insertar una fila por petición HTTP: destroza el rendimiento y llena el disco de *parts* pequeñas que luego hay que mergear.
Vamos a generar datos sintéticos aprovechando `system.numbers`:
```sql
INSERT INTO blog.pageviews
SELECT
now() - toIntervalSecond(number % 86400) AS timestamp,
number % 10000 AS user_id,
['/', '/post/foo', '/post/bar', '/about', '/search'][number % 5 + 1] AS path,
['google.com', 'direct', 'twitter.com', 'hn'][number % 4 + 1] AS referrer,
['ES', 'FR', 'DE', 'US', 'MX'][number % 5 + 1] AS country,
['mobile', 'desktop', 'tablet'][number % 3 + 1] AS device,
rand() % 30000 AS duration_ms
FROM system.numbers
LIMIT 1000000;
```
Un millón de filas generadas en décimas de segundo. `system.numbers` es una tabla virtual infinita que devuelve enteros consecutivos: una herramienta fantástica para pruebas.
## Tus primeros SELECT
```sql
-- Cuántas filas tenemos
SELECT count() FROM blog.pageviews;
-- Top 5 páginas
SELECT path, count() AS views
FROM blog.pageviews
GROUP BY path
ORDER BY views DESC
LIMIT 5;
-- Tráfico por país y hora
SELECT
country,
toStartOfHour(timestamp) AS hour,
count() AS views,
avg(duration_ms) AS avg_ms
FROM blog.pageviews
GROUP BY country, hour
ORDER BY hour DESC, views DESC
LIMIT 20;
```
Fíjate en el tiempo de ejecución que te muestra el cliente al final de cada consulta (`Elapsed: 0.024 sec.`). Con un millón de filas estamos en el rango de milisegundos. Con cien millones, probablemente sigas bajo el segundo si las consultas aprovechan la clave de ordenación.
## Qué ha cambiado respecto a PostgreSQL
Si vienes de PostgreSQL, en este punto notarás varias cosas raras:
- No has creado ningún índice y las consultas vuelan.
- No hay `SERIAL`, `AUTO_INCREMENT` ni `nextval`. En ClickHouse no se usan.
- `INSERT` multilínea de un millón de filas tarda un suspiro y no bloquea nada.
- No hay que hacer `VACUUM` ni `ANALYZE`.
- Hay un tipo llamado `LowCardinality` que PostgreSQL no tiene.
Todo esto tiene una explicación y está conectado al diseño columnar. Lo veremos en detalle en la siguiente entrega.
## Por dónde seguir
Ya tienes un ClickHouse corriendo, sabes meter datos y consultarlos. En los próximos posts de la serie:
- **[II: tipos de datos y MergeTree](/post/clickhouse-desde-cero-ii-tipos-de-datos-y-mergetree)** — el sistema de tipos, `LowCardinality`, `Nullable`, y cómo elegir la clave de ordenación.
- **[III: consultas analíticas en profundidad](/post/clickhouse-desde-cero-iii-consultas-analiticas-en-profundidad)** — funciones de agregación, arrays, window functions, sampling.
- **[IV: materialized views, projections y TTL](/post/clickhouse-desde-cero-iv-materialized-views-projections-y-ttl)** — pre-agregar datos y que una consulta de 10 segundos tarde 10 milisegundos.
- **[V: producción, replicación y clusters](/post/clickhouse-desde-cero-v-produccion-replicacion-y-clusters)** — ReplicatedMergeTree, ClickHouse Keeper, sharding, backups y monitorización.
Si estás pensando en introducir ClickHouse en un proyecto real, la [comparativa con PostgreSQL](/post/clickhouse-para-desarrolladores-que-vienen-de-postgresql) te ayudará a justificar la decisión ante el equipo.
---
# Go generics en la práctica: cuándo sí y cuándo no
URL: https://javiervalencia.net/post/go-generics-en-la-practica
Los generics llegaron a Go en la versión 1.18, marzo de 2022. Llevamos más de tres años con ellos en producción y, visto con perspectiva, creo que la comunidad ha reaccionado de una forma bastante sana: hubo una oleada inicial de uso excesivo, un rebote de "mejor no usarlos nunca", y ahora estamos en un punto intermedio más razonable. Este post es mi intento de destilar ese punto intermedio: cuándo los generics aportan y cuándo sobran.
## Por qué Go tardó tanto en incorporarlos

Go nació en 2007 con una filosofía clara: simplicidad sobre abstracción. El equipo original consideraba que los generics, tal como estaban implementados en Java o C++, añadían complejidad sin justificarla. Durante diez años la respuesta oficial fue "si necesitas generics, estás haciendo algo mal: usa interfaces, code generation o directamente copia el código".
Esta postura fue cambiando a medida que se acumulaban casos donde las interfaces no llegaban. Cualquiera que haya escrito una función `Min` o `Max` en Go antes de 1.18 sabe de qué hablo: o escribes una versión por tipo, o metes `any` y `reflect`, o usas un generador de código. Ninguna de las tres es buena. El propio equipo de Go acabó reconociendo que había un hueco real y se puso a trabajar en una propuesta que encajara con el resto del lenguaje.
## La propuesta actual: constraints y type parameters
Los generics en Go se basan en dos conceptos: *type parameters* y *constraints*. Un type parameter es un marcador genérico (por convención, `T`, `K`, `V`). Una constraint limita qué tipos pueden sustituirlo.
La función `Min` en generics queda así:
```go
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
```
`constraints.Ordered` viene del paquete `golang.org/x/exp/constraints` (ahora también hay uno estándar más limitado en `cmp`). Incluye todos los tipos comparables con `<`: ints, floats, strings.
La constraint puede ser una interfaz cualquiera, una unión de tipos, o una combinación:
```go
type Number interface {
int | int32 | int64 | float32 | float64
}
func Sum[T Number](values []T) T {
var total T
for _, v := range values {
total += v
}
return total
}
```
La sintaxis con `|` permite unir tipos concretos. El `~` (tilde) que a veces verás en ejemplos más avanzados permite aceptar también tipos derivados: `~int` acepta `int` pero también `type UserID int`. Sin el `~` esas definiciones quedarían fuera y te llevarías una sorpresa.
## Dónde sí uso generics

Hay tres casos donde los uso sin dudar:
### Estructuras de datos genéricas
Colas, pilas, conjuntos, árboles, cachés. Cualquier cosa que almacene elementos de un tipo arbitrario. Antes usabas `interface{}` y casteabas al sacar. Ahora:
```go
type Set[T comparable] struct {
items map[T]struct{}
}
func NewSet[T comparable]() *Set[T] {
return &Set[T]{items: make(map[T]struct{})}
}
func (s *Set[T]) Add(item T) {
s.items[item] = struct{}{}
}
func (s *Set[T]) Contains(item T) bool {
_, ok := s.items[item]
return ok
}
```
El consumidor obtiene type safety y autocompletado en el IDE. No hay castings innecesarios ni errores en tiempo de ejecución del tipo "expected string, got int".
### Utilidades sobre slices y maps
El paquete `slices` de la stdlib (añadido en 1.21) es un buen ejemplo de donde brillan los generics. `slices.Contains`, `slices.Index`, `slices.Sort`... todo tipado y sin reflect.
Antes tenías que escribir tu propio `Contains` para cada tipo, o usar reflect con un impacto de rendimiento brutal. Ahora:
```go
if slices.Contains(emails, "juan@example.com") {
// ...
}
```
Sin boilerplate, sin performance hit. Igual con `maps.Keys`, `maps.Values`, `maps.Copy`. Son funciones que querías tener desde Go 1.0 y que por fin están ahí.
### Patrones funcionales básicos
`Map`, `Filter`, `Reduce` sobre colecciones. Es uno de los casos donde Go siempre sufrió comparado con Python, Ruby o JavaScript.
```go
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
names := Map(users, func(u User) string { return u.Name })
```
Es código claro, seguro en tipos y sin ceremonia. La alternativa con un `for` explícito también es válida (y a veces más idiomática en Go), pero cuando tienes pipelines de transformaciones encadenadas el estilo funcional gana en legibilidad.
## Dónde no los uso
Aquí es donde me he ido volviendo conservador con el tiempo. Casos donde técnicamente podrías usar generics pero el resultado es peor que la alternativa simple.
### Cuando una interfaz llega
Si tu función solo necesita que el argumento tenga un método concreto, una interfaz es más simple:
```go
// Bien: interfaz
type Writer interface {
Write(p []byte) (int, error)
}
func LogTo(w Writer, msg string) { ... }
// Mal: generic por forzar
func LogTo[T Writer](w T, msg string) { ... }
```
El generic no añade nada salvo complejidad en la signatura. Las interfaces son el mecanismo idiomático de Go para polimorfismo; no lo tires por la borda solo porque tengas una herramienta nueva.
### Para ahorrarte dos funciones
He visto código así en más de una revisión:
```go
func Process[T int | string](value T) T {
switch v := any(value).(type) {
case int:
// lógica para int
case string:
// lógica para string
}
return value
}
```
Si dentro haces type switch sobre `T`, no estás usando generics: estás reimplementando `interface{}` con más pasos y perdiendo type safety en el proceso. Dos funciones concretas son mejores que una genérica con un switch dentro.
### En APIs públicas sin un patrón claro
Un type parameter en la signatura de una función exportada obliga a todos tus usuarios a pensar en ello. Si no tienes un caso de uso claro y estable, deja el parámetro como `any` o define una interfaz y espera a ver qué necesita la gente.
Revisar y simplificar una API pública es caro; añadir parámetros genéricos por si acaso es una invitación a arrepentirte en seis meses.
## El coste que no se habla

Los generics en Go tienen un coste que casi nadie menciona: el código genérico se compila más despacio y, según el caso, se ejecuta más despacio que la versión especializada.
El compilador usa una estrategia híbrida (GC shapes) que genera una implementación por "forma" de tipo, no una por tipo concreto. Esto ahorra tamaño de binario, pero introduce niveles de indirección que el JIT de un lenguaje dinámico no tiene, ni la monomorfización total de C++.
En la mayoría de código esto no importa. En un hot path que procesa millones de elementos, sí. Si tu función es crítica, mídela. En mis benchmarks he visto desde empates hasta un 20% más lento con generics frente a una versión manual por tipo concreto. En un servicio web que hace un millar de peticiones por segundo, eso puede marcar la diferencia entre un nodo y dos.
## Patrones útiles que he encontrado
Dos patrones que en otros lenguajes son idiomáticos y que en Go con generics se vuelven viables:
### Optional[T]
```go
type Optional[T any] struct {
value T
ok bool
}
func Some[T any](v T) Optional[T] { return Optional[T]{value: v, ok: true} }
func None[T any]() Optional[T] { return Optional[T]{} }
func (o Optional[T]) Get() (T, bool) { return o.value, o.ok }
```
En Go lo idiomático sigue siendo devolver `(T, bool)` directamente, pero `Optional[T]` tiene sentido cuando quieres guardar "quizás un valor" dentro de un struct más grande sin usar punteros.
### Result[T]
```go
type Result[T any] struct {
value T
err error
}
func (r Result[T]) Unwrap() (T, error) { return r.value, r.err }
```
De nuevo, menos idiomático que `(T, error)` pero útil en canales: `chan Result[Response]` es más expresivo que dos canales paralelos.
## Mi regla mental
Antes de escribir una función genérica me hago tres preguntas:
1. ¿Necesito que esto funcione con dos o más tipos no relacionados?
2. ¿La lógica es exactamente la misma para todos esos tipos?
3. ¿Hay una interfaz que ya capture lo que necesito?
Si la respuesta a las dos primeras es sí y a la tercera es no, uso generics. Si no, uso interfaces o duplico el código. La duplicación, dentro de unos límites, es más barata que la mala abstracción.
## Lo que he aprendido
Llevo tres años con generics en el código. Mi sensación es que los uso bastante menos de lo que esperaba al principio. En cambio, en el código que sí los usa, son un alivio: `Set`, `Map`, `slices.Contains` son piezas pequeñas que aparecen todo el tiempo y que antes eran fricción constante.
Go ha hecho lo que le caracteriza: añadir una feature grande de forma conservadora, con una implementación no óptima pero tratable, y dejar que la comunidad descubra cómo usarla bien. Tres años después, creo que la conclusión es la misma que con casi todo en Go: úsalo cuando aporte algo concreto, ignóralo el resto del tiempo, y no te sientas culpable por escribir código simple.
---
# Ambrosías actualizadas: el estado del supermercado español en 2026
URL: https://javiervalencia.net/post/ambrosias-actualizadas-el-estado-del-supermercado-espanol-en-2026
Tengo una página en este blog dedicada a las ambrosías: productos del supermercado que considero manjares a precios razonables. La creé hace un tiempo con un criterio sencillo: cosas que compro regularmente, que me parecen extraordinariamente buenas para lo que cuestan, y que cualquiera puede encontrar en un supermercado normal. Pero los precios han cambiado, algunos productos han desaparecido y he descubierto otros nuevos. Toca actualizar.
## El subidón de precios

Vamos a hablar del elefante en la habitación: los precios han subido de una forma que parece mentira. Las zamburiñas en salsa de vieira de DANI que tenía en mi lista a dos euros ahora están a dos con ochenta. El gazpacho de Carrefour que costaba un euro setenta y cinco ahora ronda los dos con veinte. El salmón ahumado de Mercadona ha pasado de cuatro cincuenta a más de cinco euros según el peso.
No son subidas del diez o el quince por ciento. Son subidas del treinta o cuarenta por ciento en dos años. Y lo peor es que se han quedado ahí. La inflación oficial ha bajado pero los precios en el supermercado no han vuelto a donde estaban. Subieron con la excusa de los costes de transporte, de la energía, de la cadena de suministro, y cuando esas excusas desaparecieron los precios se quedaron arriba.
## Lo que ha desaparecido
Hay productos que directamente ya no encuentro. No sé si han dejado de fabricarlos, si mi supermercado ha dejado de traerlos o si los han sustituido por otra cosa, pero el caso es que he ido varias veces a buscarlos y no están.
La melva canutera de Ubago sigue existiendo pero ha cambiado de formato. Ahora viene en latas más pequeñas a prácticamente el mismo precio, que es la forma elegante que tiene la industria alimentaria de subir precios sin que parezca que los ha subido. El producto sigue siendo bueno pero la relación calidad-precio ya no es lo que era.
También he notado que las marcas blancas han cambiado. Mercadona ha reformulado varios de sus productos de Hacendado y no siempre a mejor. Hay cosas que antes eran indistinguibles de la marca y ahora tienen un sabor diferente, más artificial, como si hubieran ajustado las recetas para ahorrar en ingredientes.
## Nuevos descubrimientos

No todo son malas noticias. He descubierto algunas ambrosías nuevas que merecen estar en la lista:
Las conservas de berberechos al natural de la marca Rianxeira. No son baratas si las comparas con otros productos del supermercado, pero si las comparas con lo que te cobran por unos berberechos en un restaurante son una ganga. Unos tres euros por una lata que tiene un sabor a mar que es difícil de superar. Abiertos, con un chorrito de limón, son una cena perfecta.
El hummus de garbanzos de Mercadona. Un euro y poco por un bote que da para dos o tres raciones generosas. No es el mejor hummus del mundo, no voy a decir eso, pero es sorprendentemente bueno para ser un producto de marca blanca. Con unos picos de pan o unas crudités de zanahoria tienes un aperitivo que parece que te has currado mucho más de lo que realmente te has currado.
El queso curado de oveja de Lidl. Esto es un hallazgo reciente. Lidl tiene una línea de quesos curados españoles que están por debajo de los seis euros la cuña y que compiten de tú a tú con quesos que cuestan el doble en tiendas especializadas. El de oveja en particular tiene un punto de intensidad y una textura que no esperas por ese precio.
Y el café molido de Bonka, que lleva toda la vida existiendo pero al que no le había prestado atención hasta que me cansé de pagar cuatro euros por café de marca. Dos euros y medio por un paquete que me dura dos semanas. No es café de especialidad ni lo pretende, pero es un café honesto, consistente, que sabe a café y no a agua teñida.
## La estrategia del comprador
Con los precios como están, he cambiado mi forma de comprar. Antes iba a un supermercado y compraba todo allí. Ahora hago lo que llamo "la ruta de las ambrosías": las conservas en Mercadona, la fruta y la verdura en la frutería del barrio, el queso en Lidl, la carne en la carnicería. Es más trabajo pero la diferencia en el ticket mensual es notable.
También he aprendido a mirar el precio por kilo en vez del precio por unidad. Los supermercados juegan con los formatos para confundirte: una lata más grande no siempre es más barata por kilo. A veces el formato pequeño es mejor negocio. A veces el formato familiar es una trampa para que compres más de lo que necesitas y una parte acabe en la basura.
Otra cosa que he aprendido: las ofertas del tipo "segunda unidad al cincuenta por ciento" solo son buenas si ibas a comprar dos unidades de todas formas. Si compras la segunda solo porque está de oferta, no estás ahorrando: estás gastando más de lo que habías planeado. Los supermercados lo saben. Por eso ponen esas ofertas.
## La ambrosía definitiva

Si tuviera que quedarme con una sola ambrosía, con un solo producto del supermercado que ejemplifique lo que busco, serían las zamburiñas de DANI. Incluso con la subida de precio. Abres la lata, las calientas treinta segundos en una sartén con su propia salsa, y tienes algo que en un restaurante te costaría ocho o diez euros. En casa te ha costado menos de tres. Son tiernas, la salsa tiene un punto de intensidad justo, y le dan un toque de lujo a cualquier cena del martes.
Eso es para mí una ambrosía: algo cotidiano que te hace sentir que vives bien. No necesitas un restaurante con estrella Michelin para comer de maravilla. Necesitas saber qué comprar, dónde comprarlo y cómo prepararlo. El supermercado está lleno de pequeños lujos esperando a que les prestes atención.
Seguiré actualizando la lista. Los precios seguirán subiendo, algunos productos desaparecerán y otros nuevos llegarán. Pero la filosofía se mantiene: comer bien no debería ser un privilegio. Es cuestión de prestar atención.
---
# La cerveza de los domingos: por qué las amistades adultas necesitan rituales
URL: https://javiervalencia.net/post/la-cerveza-de-los-domingos-con-amigos
Con mis amigos más cercanos, los que llevo conociendo veinte años o más, he establecido en los últimos tiempos un ritual que a primera vista puede sonar cursi pero que funciona mejor que cualquier otra cosa que hayamos intentado: una cerveza los domingos al mediodía, siempre que podemos, sin agenda, sin planes posteriores, sin compromiso de que pase nada interesante. Y desde que lo hacemos, tengo la sensación de que las amistades están mejor que nunca.
Esto merece una explicación, porque yo mismo habría dicho hace cinco años que esta idea era innecesaria.
## Lo que creíamos a los veinte

Cuando éramos jóvenes, las amistades pasaban solas. Compartíamos piso, compartíamos universidad, compartíamos horarios de trabajo, compartíamos bares. Te ibas de cañas un martes porque ibas a coincidir igual. No había que planificar nada. La amistad era el aire que respirabas mientras hacías otras cosas.
Esa es la versión de la amistad que seguimos teniendo en la cabeza cuando pasamos los treinta. Y es la versión que, sistemáticamente, se va descomponiendo sin que nadie lo reconozca en voz alta. Uno se muda de ciudad. Otro tiene un hijo. Otra cambia de trabajo y no tiene horarios compatibles. Un tercero entra en una relación que absorbe su tiempo. Sin que nadie haga nada mal, el contacto natural deja de existir.
El error está en pensar que, como la amistad seguía viva sola antes, seguirá viva sola ahora. No lo hará.
## Lo que cambia después de los treinta
Después de los treinta, y mucho más después de los cuarenta, el tiempo es un recurso escaso. Entre trabajo, pareja, hijos, padres mayores, salud, hobbies y las mil microtareas que la vida adulta impone, el tiempo libre no planificado es casi inexistente. Y lo que no se planifica, no ocurre.
Las amistades de largo recorrido no compiten con tus otras amistades: compiten con el agotamiento del viernes por la noche, con la pereza del sábado por la mañana, con los mil motivos legítimos para no moverte. Si la amistad no tiene un hueco fijo en la semana, el hueco se llena con otra cosa. No por mala voluntad, por inercia.
Esto es lo que la "cerveza de los domingos" arregla.
## Por qué los domingos y por qué a mediodía

Probamos otros horarios. Los viernes por la noche la gente llega cansada, con el trabajo encima, queriendo irse pronto para pasar tiempo con la familia. Los sábados están llenos de eventos, cumpleaños, planes familiares. Los domingos por la tarde la gente empieza a pensar en el lunes y se pone cenizo.
Los domingos a mediodía son un hueco que casi nadie reclama. No interfiere con los planes de pareja del sábado. No interfiere con las cenas familiares. No interfiere con los compromisos infantiles (salvo partidos de fútbol). La mayoría de la gente tiene dos o tres horas libres entre las doce y las tres. Y esas dos horas son suficientes.
El mediodía tiene otra virtud: se bebe con moderación. Una cerveza, o dos como mucho. Sales del bar a las dos o tres de la tarde, comes en casa, la tarde sigue normal. No hay resaca, no hay tiempo perdido, no hay "ya voy a casa a dormir la siesta". Es una forma de convivir con amigos compatible con tener una vida ordenada.
## Por qué sin plan
La regla más importante del ritual es que no tiene agenda. No se cita para hablar de nada concreto. No se celebra nada. No hay invitados especiales. No hay plan para después. Literalmente: una cerveza, en el mismo bar, a la misma hora, con las mismas dos o tres personas.
Esto es contraintuitivo. La lógica adulta es que si vas a invertir tiempo, tiene que ser para algo. Pero las amistades profundas no crecen con eventos especiales: crecen con tiempo ordinario compartido. Es en las conversaciones sin propósito donde te enteras de que alguien está pasando por algo en el trabajo, de que un padre está enfermo, de que tiene dudas con un proyecto. Esas cosas no se cuentan en la cena de cumpleaños. Se cuentan cuando el tema sale solo en una conversación sobre otra cosa.
Las amistades necesitan ancho de banda, no calidad de banda. Y el ancho de banda se consigue con regularidad, no con intensidad.
## La logística invisible

Un ritual así no se mantiene solo. Hay una logística invisible que funciona porque alguien la asume. Una persona (casi siempre la misma) confirma por el grupo el sábado por la noche. Alguien elige el bar una vez fijo y luego no se discute más. Si uno no puede ir una semana, no se cancela: los que pueden van igual. Si llueve, hay bar cubierto. Si hay un puente o vacaciones, se salta pero se recupera.
La única regla sagrada es que no hay debate sobre si hacerlo o no. No se pregunta "¿vamos este domingo?" cada semana. Se asume que se va. Quien no puede, avisa. Los demás están allí.
Este automatismo es lo que lo hace funcionar. Si tuvieras que reorganizar la cita cada vez, la fricción social acabaría con el ritual en tres meses. Con la regla de "siempre es igual", la fricción es cero y el compromiso es mínimo.
## Lo que he aprendido en este tiempo
Después de un año largo con el ritual ya instaurado, hay varias cosas que he notado:
**Mi estado de ánimo es mejor los lunes.** No es sugestión. Es algo concreto: pasar dos horas con gente que te conoce de verdad, sin pantallas, sin prisa, cambia el inicio de la semana.
**Los problemas se gestionan antes.** Cuando un amigo está pasando algo, lo comenta. Cuando es algo que no cuenta en el mismo momento, lo cuenta la semana siguiente. No hay que esperar al cumpleaños o a la cena anual.
**Nos apoyamos mejor.** Saber de verdad lo que está pasando en la vida de alguien te permite apoyar con precisión. Un "¿cómo va lo del trabajo?" dicho con conocimiento de causa es muy distinto de un "¿qué tal todo?" genérico.
**Mis hijas lo notan.** Han aprendido que su padre tiene amigos que ve todas las semanas, que las amistades no son algo abstracto sino algo concreto que se cultiva. Es el tipo de modelo que no se enseña hablando.
## Lo que no recomiendo
No todo ritual es igual. Algunos consejos de lo que no funciona:
**Grupos grandes.** Por encima de cinco personas el ritual se convierte en un evento. Se mueve la cita, se debate dónde ir, no todos se conocen igual. Mejor dos o tres personas fijas.
**Cambiar de sitio.** Aunque suene divertido variar, la rutina es lo que lo hace automático. Un solo bar, siempre el mismo. La novedad no aporta; la familiaridad sí.
**Hacerlo evento.** En cuanto invitas a un cuarto "porque está en la ciudad", o pides mesa grande, o encargas algo especial, deja de ser un ritual y pasa a ser una cosa. Y las cosas se cancelan con facilidad; los rituales no.
## La alternativa que no nombramos
La alternativa a tener un ritual de amistad es la que vive la mayoría de gente que conozco. Ven a los amigos cuando hay un cumpleaños. Se escriben por WhatsApp cuando se acuerdan. Se ven tres o cuatro veces al año y siempre dicen "tenemos que vernos más". Y no se ven más, porque el "más" implica ponerse de acuerdo sobre una fecha, un sitio, una razón.
Esto no es culpa de nadie. Es lo que pasa cuando la amistad depende solo de la voluntad individual en una vida adulta sobrecargada. La única manera de romper el patrón es meterlo en el calendario y protegerlo como proteges una reunión importante.
## Por qué lo recomiendo
Si tienes amigos que valoras y te has descubierto diciéndote que deberías verlos más, plantéate un ritual. No tiene que ser una cerveza los domingos: puede ser un desayuno los miércoles, un paseo los sábados, una partida de cartas los jueves. Lo importante es que sea regular, que sea en el mismo sitio, que sea con las mismas personas, y que dure suficiente para que las conversaciones se suelten.
La amistad adulta no es como la de los veinte años. Las que sobreviven no sobreviven por azar: sobreviven porque alguien, en algún momento, decide que son importantes y las trata como tal. Una cerveza los domingos es poco. Pero sostenida en el tiempo, es mucho.
---
# Kamailio en 2026: por qué sigue siendo relevante
URL: https://javiervalencia.net/post/kamailio-en-2026-por-que-sigue-siendo-relevante
Kamailio es un servidor SIP de alto rendimiento que nació en 2001 como SER (SIP Express Router) en el instituto de investigación alemán FhG Fokus. Veinticinco años después, sigue siendo la pieza central de la mayoría de plataformas VoIP serias del mundo. Operadores de telecomunicaciones, proveedores de VoIP, plataformas de comunicaciones unificadas: por debajo, casi siempre hay un Kamailio.
No es la herramienta más fácil de aprender. No tiene una interfaz web bonita. Su fichero de configuración se parece más a un lenguaje de programación que a un fichero de configuración. Pero hace algo que ninguna otra herramienta hace igual de bien: procesar miles de transacciones SIP por segundo con una fiabilidad que roza lo absurdo.
## Qué hace Kamailio (y qué no hace)

Kamailio es un proxy SIP. Recibe mensajes SIP (INVITE, REGISTER, BYE...), decide qué hacer con ellos según las reglas que le configures, y los reenvía al destino correcto. Es el director de tráfico de una red VoIP.
Lo que Kamailio no hace es procesar media (audio, vídeo). No mezcla llamadas, no hace conferencias, no reproduce música en espera. Para eso necesitas un media server como Asterisk o FreeSWITCH. Kamailio se encarga del plano de señalización y deja el plano de media a otros.
Esta separación es clave para entender por qué Kamailio escala tan bien. El procesamiento de señalización SIP es barato computacionalmente: son mensajes de texto que pesan unos pocos kilobytes. El media es caro: son flujos de audio en tiempo real que consumen CPU y ancho de banda. Al separar las dos funciones, puedes tener un Kamailio manejando 10.000 llamadas simultáneas en un servidor modesto, mientras los media servers se escalan horizontalmente según la demanda.
## El fichero de configuración
La configuración de Kamailio es su punto fuerte y su punto débil. Es un lenguaje propio, una mezcla entre C y un scripting language, con bloques de routing, variables, funciones de módulo y control de flujo:
```
request_route {
# Registros
if (is_method("REGISTER")) {
if (!www_authorize("voiper.es", "subscriber")) {
www_challenge("voiper.es", "1");
exit;
}
save("location");
exit;
}
# Llamadas
if (is_method("INVITE")) {
# Autenticar
if (!proxy_authorize("voiper.es", "subscriber")) {
proxy_challenge("voiper.es", "1");
exit;
}
consume_credentials();
# Enrutar
if (!lookup("location")) {
sl_send_reply("404", "Not Found");
exit;
}
t_relay();
}
}
```
Esto es un ejemplo mínimo que autentica registros y llamadas, busca el destino en la tabla de localización y reenvía. En producción, un fichero de Kamailio puede tener miles de líneas con lógica de enrutamiento compleja: least cost routing, balanceo de carga entre gateways, detección de fraude, rate limiting, NAT traversal, geolocalización...
La curva de aprendizaje es pronunciada. No hay asistentes de configuración ni interfaces gráficas que generen la configuración por ti. Tienes que entender SIP, entender los módulos de Kamailio y escribir la lógica tú mismo. Pero esa complejidad es también lo que te da control total sobre cada aspecto del enrutamiento.
## Por qué nadie lo ha reemplazado

Han pasado veinticinco años y no hay un reemplazo real para Kamailio. Las razones son varias:
**Rendimiento probado**: Kamailio procesa miles de transacciones por segundo en hardware modesto. Está escrito en C, es single-threaded por diseño (con workers para tareas pesadas) y tiene un footprint de memoria mínimo. No hay ningún proxy SIP que se le acerque en rendimiento bruto.
**Flexibilidad absoluta**: cualquier lógica de enrutamiento imaginable se puede implementar en Kamailio. ¿Quieres enrutar llamadas según la hora del día, el país de origen, el saldo del cliente y la carga de los gateways? Se puede. ¿Quieres implementar un algoritmo de detección de fraude en tiempo real? Se puede. No hay límites prácticos.
**Ecosistema maduro**: más de 200 módulos disponibles. Bases de datos (MySQL, PostgreSQL, Redis, MongoDB), protocolos (WebSocket, TLS, SCTP), funcionalidades (presencia, mensajería, estadísticas). Lo que necesites, probablemente hay un módulo que lo hace.
**Comunidad activa**: el proyecto sigue en desarrollo activo. Hay releases regulares, una mailing list activa y una comunidad de operadores que comparten conocimiento. El Kamailio World Conference se celebra cada año.
## Kamailio en mi stack
En VoIPer Telecom, Kamailio es la pieza central de toda la infraestructura de voz. Hace de punto de entrada para todos los clientes SIP, gestiona la autenticación contra la base de datos, enruta las llamadas entrantes y salientes, balancea el tráfico entre los media servers y monitoriza el estado de los gateways.
La arquitectura típica que uso es:
```
Clientes SIP → Kamailio (proxy/registrar) → Asterisk (media/PBX)
↓
PostgreSQL (usuarios, CDR)
↓
ClickHouse (analytics)
```
Kamailio se encarga de todo lo que es señalización: registros, autenticación, enrutamiento, balanceo, NAT traversal. Asterisk se encarga del media: IVR, buzones de voz, conferencias, grabación. PostgreSQL almacena los usuarios y los CDR (Call Detail Records). ClickHouse recibe los CDR para análisis y facturación.
Esta separación me permite escalar cada componente de forma independiente. Si necesito más capacidad de media, añado otro Asterisk. Si necesito más capacidad de señalización, optimizo Kamailio o añado otro nodo con dispatching.
## Cuándo tiene sentido usarlo

Kamailio tiene sentido cuando:
- **Manejas más de cien llamadas simultáneas**: por debajo de eso, Asterisk solo es suficiente.
- **Necesitas lógica de enrutamiento compleja**: least cost routing, failover, balanceo, manipulación de headers.
- **Operas con múltiples media servers**: Kamailio actúa como balanceador.
- **Necesitas alta disponibilidad**: Kamailio soporta clustering activo-activo.
- **Procesas tráfico de múltiples clientes**: el modelo multi-tenant se implementa naturalmente en el routing script.
No tiene sentido cuando solo necesitas una centralita para una oficina de veinte personas. Para eso, Asterisk o FreePBX son más que suficientes.
## Conclusión
Kamailio no es sexy. No tiene un logo moderno ni un marketing team. No sale en Hacker News. Pero es la herramienta que sostiene la infraestructura VoIP de medio mundo, y la razón por la que puedes hacer una llamada VoIP y que funcione de forma fiable es, en gran parte, porque alguien configuró un Kamailio correctamente.
Si trabajas en telecomunicaciones o VoIP, aprender Kamailio es una de las mejores inversiones que puedes hacer en tu carrera. Hay pocos profesionales que lo dominen y mucha demanda. La curva de aprendizaje es dura, pero una vez que la superas, tienes una herramienta que puede con cualquier cosa que le eches.
---
# Tapeo por el casco histórico de Mijas pueblo
URL: https://javiervalencia.net/post/tapeo-por-el-casco-historico-de-mijas-pueblo
Mijas pueblo tiene dos caras. La de las postales, con los burros taxi, las casas blancas, los geranios perfectamente colocados y los autocares aparcados en la Virgen de la Peña. Y la que conocemos los que vivimos por aquí: un pueblo pequeño, empinado, con calles que de tres metros en tres metros te cambian la vista, y con un puñado de bares que llevan décadas abiertos y que siguen siendo tan buenos como el primer día.
Este post es sobre esa segunda cara. Una ruta de tapeo como la que hago con amigos un sábado cualquiera cuando no quiero bajar a la costa.
## Por qué subir a Mijas pueblo para tapear

La Cala, Fuengirola o Torremolinos están llenos de bares buenos. No hace falta moverse para comer bien. Pero Mijas pueblo tiene algo que la costa no tiene: es un pueblo, no una urbanización extendida. Las calles son peatonales, los locales llevan abiertos generaciones, los camareros te reconocen si vas una vez al mes. El ritmo es distinto. Comes con más calma.
Además, la temperatura en Mijas pueblo es siempre cinco grados menos que en la costa. En julio, eso marca la diferencia entre disfrutar una tapa en la calle o sudarla. En enero, la diferencia juega en contra; pero hay chimeneas en varios sitios y pasa a ser parte del encanto.
## El arranque: cerveza y aceituna en El Mirlo Blanco
Empiezo siempre en el mismo sitio: la plaza de la Libertad. Hay varios bares en la misma plaza, pero mi favorito para arrancar es **El Mirlo Blanco**. No es el más barato, no tiene la mejor carta, pero tiene la terraza con mejor vista del pueblo. Una caña con una aceituna gordal y estás mirando el mar desde 450 metros de altitud. Es difícil pedir más para empezar una tarde.
La trampa turística aquí es obvia: pedir el menú completo. Es un sitio para la primera cerveza, no para cenar. Una caña, una aceituna, y cuando te has bebido la mitad te levantas y sigues calle abajo. En media hora habrás gastado tres euros y te llevas la mejor vista del día.
## Segunda parada: el boquerón en La Alcazaba

Bajando por la calle San Sebastián, a mitad de cuesta, hay un sitio pequeño, sin cartelería llamativa, con tres mesas dentro y dos fuera: **La Alcazaba**. Lo lleva una pareja desde hace una eternidad. Él en la cocina, ella en la barra. No hay mucho más.
Aquí lo que se pide es el boquerón, de dos formas: frito y en vinagre. El frito es crujiente, sin aceite de más, con una pizca de sal gorda y limón aparte por si quieres. El de vinagre es cortado fino, con ajo fresco laminado y un chorro justo de aceite. Cuesta cuatro euros y medio la tapa, lo mismo que hace diez años. Con otra caña estás en siete con cincuenta.
Si vas un sábado a las dos y media es difícil pillar mesa. Si vas a las cinco y media la tienes asegurada. Y se come mejor cuando la cocina no va con agobios.
## La parada larga: chacina y queso en La Muralla
La Muralla es mi sitio para la tercera parada, la que ya dura más. Es un local antiguo, suelo de baldosa hidráulica, paredes con fotos en blanco y negro del pueblo en los años sesenta, y una barra donde caben unas seis personas con tapas y platitos.
Tienen una carta de chacina cortada a cuchillo que es de las mejores que conozco en la provincia. El jamón ibérico de bellota lo traen de una finca de Cortes de la Frontera. La caña de lomo ibérico es una de esas cosas que pides "solo una" y acabas pidiendo dos. Cuatro lonchas por cuatro euros: no es regalado pero es honrado.
Combínalo con queso curado de oveja manchega (también cortado a cuchillo) y una copa de un rioja joven y tienes una media hora larga sentado sin que pase el tiempo. Aquí es donde la conversación empieza a soltarse y donde se cuentan los chismes y los planes que no se cuentan en una oficina.
## Salto de nivel: Kiosko Almendra

Hay un sitio que mucha gente no conoce y que parece lo que es: un kiosko. Está en la calle paralela a la plaza, cerca del aparcamiento. Se llama **Kiosko Almendra**. Techo de madera, cuatro taburetes altos, un cocinero que se mueve en un metro cuadrado.
Lo que hacen son montaditos calientes. Pan tostado con tomate y aceite, encima de todo tipo de cosas: presa ibérica a la brasa con cebolla pochada, pulpo con patata y pimentón, bacalao confitado con huevo de codorniz. Cada uno ronda los cinco euros. Son montaditos grandes: con dos y una caña ya estás comiendo.
La cocina es de libro. Nada rebuscado, nada de espumas ni de esferas, pero ejecutado con técnica. Es uno de esos sitios donde te das cuenta de que la diferencia entre algo correcto y algo memorable son cinco minutos más de fuego o una cantidad de sal mejor ajustada.
## La terraza para el atardecer: Bar Porras
A partir de las siete y media, si es primavera o verano, hay que subir al mirador y parar en **Bar Porras**. La terraza mira al sur y con el sol bajando la luz se vuelve dorada. Pides una cerveza, pides unas patatas bravas (aquí la salsa la hacen ellos, no es industrial), y te quedas viendo cómo cambia el color del mar durante veinte minutos.
Este es el momento del día en que Mijas pueblo justifica el viaje. Los turistas se han ido en los autocares, el pueblo se queda vacío, y la luz es la que hace que los fotógrafos paguen por estar en la Costa del Sol.
## Para cerrar: el postre que no esperas
Cerrar una ruta de tapeo con postre no es lo habitual. En mi caso siempre termino en **Heladería Valverde**, en la calle Málaga. Es una heladería pequeña, de las que hacen el helado detrás del mostrador. Los sabores cambian con la temporada: en abril tienen el de yogur con miel de Benamargosa y el de pistacho con sal. Los dos son espectaculares.
Un cucurucho de dos bolas por tres euros y cincuenta céntimos y te lo comes andando hasta el coche. Punto final de la tarde.
## Lo que no hago y por qué
Hay cosas que no incluyo en la ruta y que muchas guías sí:
**Los restaurantes con "vista panorámica al mar"** sobre la calle Carril. Son los más visibles, los más caros y, casi sin excepción, los peores. La calidad del producto baja proporcional al precio de la vista. Me he quemado varias veces, he aprendido.
**Los sitios con fotos plastificadas de las tapas en la puerta.** Regla universal: si tienen que enseñarte fotos, es porque la calidad no se defiende sola. Los sitios buenos tienen una pizarra o nada.
**Los bares que "hacen paella" en paelleras gigantes expuestas a la calle.** La paella es un plato que se hace al momento y se come al momento. Lo que está en la paellera grande a las ocho de la tarde es una reconstrucción que nadie pidió.
## El presupuesto de la ruta
Una tarde completa por las cinco paradas anteriores, con dos personas, pidiendo algo en cada sitio, suele salir por entre ochenta y cien euros. No es barato, pero tampoco es caro para lo que se come y para el tiempo que se pasa. Si quieres ajustar, reduce a tres paradas en vez de cinco; si quieres subir, pide una media ración más en Kiosko Almendra o en La Muralla.
## Por qué esta ruta y no otra
Hay otras rutas posibles en Mijas pueblo. Hay gente que defiende la de la calle Los Caños con bares más pequeños, hay gente que solo va al restaurante El Mesón Asturiano porque es "de toda la vida". Todas son válidas. La mía tiene un criterio que he afinado con los años: priorizar producto sobre decoración, priorizar bares que lleven abiertos décadas, y buscar el equilibrio entre la vista y la cocina.
En Mijas pueblo es fácil pagar por el marco; lo difícil es comer bien. Esta ruta es mi manera de conseguir las dos cosas sin tener que elegir. Si algún sábado te animas a subir, cuéntame después qué tal.
---
# Rsync: el comando que uso todos los días
URL: https://javiervalencia.net/post/rsync-el-comando-que-uso-todos-los-dias
Si tuviera que quedarme con un solo comando de Linux, sería rsync. Lo uso para subir código a servidores, para hacer backups, para sincronizar directorios entre máquinas, para mover terabytes de datos sin perder nada. Es una de esas herramientas que llevan décadas existiendo, que no son llamativas, y que simplemente funcionan.
## Lo básico

Rsync sincroniza ficheros de un origen a un destino. La gracia es que solo transfiere lo que ha cambiado, no todo:
```bash
rsync -avz ./mi-proyecto/ servidor:/opt/app/
```
Los flags que uso siempre:
- `-a` (archive): preserva permisos, timestamps, links simbólicos, propietarios. Es el flag más importante.
- `-v` (verbose): muestra qué ficheros se transfieren.
- `-z` (compress): comprime los datos durante la transferencia. Útil con conexiones lentas.
Esa barra final en `./mi-proyecto/` es importante. Con la barra, rsync copia el contenido del directorio. Sin la barra, copia el directorio en sí. La diferencia entre que tus ficheros acaben en `/opt/app/` o en `/opt/app/mi-proyecto/`.
## Dry-run: mira antes de tocar
El flag más infravalorado de rsync:
```bash
rsync -avzn ./local/ servidor:/opt/app/
```
El `-n` (dry-run) muestra exactamente qué haría sin hacer nada. Es la diferencia entre un deploy tranquilo y un incidente a las tres de la mañana. Siempre hago un dry-run antes de un rsync real en producción. Siempre.
## Exclusiones

Casi nunca quieres sincronizar todo. El flag `--exclude` filtra lo que no debe ir:
```bash
rsync -avz \
--exclude='.git' \
--exclude='node_modules' \
--exclude='*.log' \
--exclude='.env' \
./proyecto/ servidor:/opt/app/
```
Para proyectos con muchas exclusiones, puedes usar un fichero:
```bash
# .rsync-exclude
.git
node_modules
tmp
*.log
.env
.env.*
__pycache__
```
```bash
rsync -avz --exclude-from='.rsync-exclude' ./proyecto/ servidor:/opt/app/
```
## --delete: el flag peligroso (y necesario)
Por defecto, rsync solo añade y actualiza. Si borras un fichero en local, sigue existiendo en el servidor. El flag `--delete` elimina del destino lo que ya no está en el origen:
```bash
rsync -avz --delete ./proyecto/ servidor:/opt/app/
```
Este flag es necesario para mantener el destino como un espejo exacto del origen, pero hay que usarlo con cuidado. Si te equivocas de directorio, puedes borrar cosas que no debías. Por eso el dry-run es sagrado:
```bash
rsync -avzn --delete ./proyecto/ servidor:/opt/app/
# Revisar la salida
# Si todo está bien:
rsync -avz --delete ./proyecto/ servidor:/opt/app/
```
## Backups incrementales

Rsync es perfecto para backups porque solo copia lo que ha cambiado. Con `--link-dest` puedes hacer backups incrementales que parecen completos pero ocupan una fracción del espacio:
```bash
#!/bin/bash
DATE=$(date +%Y-%m-%d)
LATEST=/backups/latest
DEST=/backups/$DATE
rsync -avz --delete --link-dest=$LATEST servidor:/opt/app/data/ $DEST/
ln -snf $DEST $LATEST
```
Cada backup es un directorio completo con todos los ficheros. Pero los ficheros que no han cambiado son hard links al backup anterior, así que no ocupan espacio extra. Puedes entrar en cualquier backup y ver el estado completo del sistema en ese momento, pero el almacenamiento total es solo la suma de los cambios.
## Limitar el ancho de banda
En producción, no quieres que un rsync de 50GB sature la conexión:
```bash
rsync -avz --bwlimit=10000 ./datos/ servidor:/opt/datos/
```
`--bwlimit=10000` limita a 10MB/s. Suficiente para que la transferencia avance sin afectar al tráfico real del servidor.
## Reanudar transferencias
Si una transferencia grande se interrumpe, rsync puede reanudarla:
```bash
rsync -avz --partial --progress ./imagen.iso servidor:/tmp/
```
`--partial` mantiene los ficheros parcialmente transferidos en vez de borrarlos. `--progress` muestra el progreso de cada fichero. La próxima vez que ejecutes el comando, rsync retoma donde lo dejó.
Para ficheros muy grandes, usa `--append-verify`:
```bash
rsync -avz --append-verify ./dump.sql.gz servidor:/backups/
```
Continúa la transferencia desde donde se quedó y verifica el checksum al final.
## SSH con puerto personalizado
Si tu servidor SSH usa un puerto diferente al 22:
```bash
rsync -avz -e 'ssh -p 2222' ./proyecto/ servidor:/opt/app/
```
También puedes especificar una clave SSH concreta:
```bash
rsync -avz -e 'ssh -i ~/.ssh/id_deploy' ./proyecto/ servidor:/opt/app/
```
## Mi script de deploy
Este es el script que uso para desplegar este blog:
```bash
#!/bin/bash
set -euo pipefail
SERVER="root@web.javiervalencia.net"
DEST="/opt/blog"
echo "==> Dry-run..."
rsync -avzn --delete \
--exclude='.git' \
--exclude='go.*' \
--exclude='cmd' \
--exclude='internal' \
--exclude='*.go' \
--exclude='deploy' \
--exclude='.env' \
--exclude='content/views.json' \
./ $SERVER:$DEST/
read -p "¿Continuar? (s/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Ss]$ ]]; then
rsync -avz --delete \
--exclude='.git' \
--exclude='go.*' \
--exclude='cmd' \
--exclude='internal' \
--exclude='*.go' \
--exclude='deploy' \
--exclude='.env' \
--exclude='content/views.json' \
./ $SERVER:$DEST/
echo "==> Reiniciando servicio..."
ssh $SERVER 'systemctl restart blog'
echo "==> Hecho."
fi
```
Primero dry-run, luego confirmación, luego el rsync real y reinicio del servicio. Simple, predecible y a prueba de errores.
## Conclusión
Rsync lleva existiendo desde 1996 y no ha necesitado reinventarse porque hace bien lo que hace. No es llamativo, no tiene una interfaz web bonita, no tiene un SaaS detrás. Es un comando que copia ficheros de forma inteligente. Y eso, en el día a día de un administrador de sistemas o un desarrollador que gestiona servidores, es más valioso que cualquier herramienta moderna con quinientas estrellas en GitHub.
---
# El paseo litoral de La Cala de Mijas en 45 minutos
URL: https://javiervalencia.net/post/el-paseo-litoral-de-la-cala-de-mijas-en-45-minutos
Me gusta caminar por el paseo litoral de La Cala de Mijas. No lo hago tanto como me gustaría, que es la frase que resume la mayoría de cosas buenas de mi vida, pero cuando lo hago siempre me alegro de haberlo hecho. Cuarenta y cinco minutos caminando a paso normal, sin prisa, sin auriculares, sin mirar el móvil. Solo yo, el mar y lo que sea que me pase por la cabeza.
## La salida

Normalmente empiezo por el lado de La Cala, junto al faro. No es un faro espectacular, no sale en las postales, pero marca el inicio de la ruta y me sirve como punto de referencia mental: cuando lo veo, sé que he empezado.
Los primeros diez minutos son los peores. El cuerpo todavía no ha entrado en ritmo, la cabeza sigue en lo que estaba haciendo antes de salir y hay una voz interna que me dice que tengo cosas más productivas que hacer. Esa voz miente. Nunca, ni una sola vez, he terminado el paseo pensando que habría sido mejor quedarme en casa delante del ordenador.
El paseo empieza pegado a la costa, con el mar a la izquierda y una fila de chiringuitos y restaurantes a la derecha. En invierno la mayoría están cerrados o medio vacíos, que es cuando más me gusta ir. Sin el ruido de la temporada alta puedes oír el mar de verdad. No como sonido de fondo sino como protagonista.
## Los primeros quince minutos
Cuando el cuerpo coge ritmo, la cabeza se vacía. No es meditación ni nada parecido, es simplemente que caminar a un ritmo constante tiene un efecto en el cerebro que no consigo con ninguna otra actividad. Los pensamientos dejan de atropellarse y empiezan a desfilar de uno en uno, de forma ordenada.
Es en estos primeros quince minutos donde suelo resolver problemas técnicos que llevaba días dándole vueltas. No porque me ponga a pensar en ellos activamente sino porque al revés: al dejar de pensar en ellos aparece la solución. Es como cuando recuerdas una palabra que tenías en la punta de la lengua justo cuando dejas de intentar recordarla.
El paseo en este tramo pasa por debajo de unos bloques de apartamentos que en verano están llenos de turistas y en invierno parecen abandonados. Hay un contraste raro entre la belleza del mar y la fealdad de algunas construcciones de los setenta que se hicieron sin pensar en otra cosa que en meter el máximo número de camas cerca de la playa. La Costa del Sol es así: belleza natural y brutalismo arquitectónico conviviendo en la misma calle.
## La mitad del camino

Sobre el minuto veinte llegas a una zona más abierta donde el paseo se separa un poco de la costa y hay un tramo con palmeras y bancos. Es mi tramo favorito. Si es por la tarde y hay sol, la luz rasante le da a todo un color dorado que no he visto en ningún otro sitio.
Aquí es donde suelo cruzarme con los habituales. Los hay de varios tipos: la señora que camina con bastón a un ritmo que parece lento pero que lleva haciendo tres kilómetros sin parar. El matrimonio de jubilados británicos con ropa deportiva de colores fluorescentes. El señor que corre con su perro y que parece que el perro lo lleva a él. La pareja joven que va con un carrito de bebé enorme.
Nos saludamos con un gesto de cabeza. No nos conocemos de nada pero compartimos algo: estamos ahí, a esa hora, haciendo lo mismo. Hay una especie de hermandad silenciosa entre la gente que camina a la misma hora. Nunca hemos hablado pero si un día dejo de ir, probablemente alguien lo note.
## Los últimos quince minutos
El tramo final es el que tiene las mejores vistas. El paseo sube ligeramente y desde arriba puedes ver la costa en ambas direcciones: hacia Fuengirola por un lado y hacia Marbella por el otro. En un día claro se ve Gibraltar y, si hay suerte, la sombra de la costa africana al fondo.
Es en este tramo donde la mente ya está completamente limpia y empiezan a aparecer las ideas buenas. No las soluciones a problemas concretos, que esas vienen antes, sino las ideas de verdad. Las que conectan cosas que no tenían relación. Las que te hacen pensar "¿y si...?" seguido de algo que no se te habría ocurrido sentado en la silla.
No las apunto. Sé que debería, pero sacar el móvil rompería algo. Confío en que las ideas buenas de verdad sobreviven al paseo y siguen ahí cuando llego a casa. Las que se olvidan probablemente no eran tan buenas.
## La vuelta

La vuelta la hago por el mismo camino. Hay gente que prefiere hacer rutas circulares, pero a mí me gusta volver por donde he venido. Todo se ve diferente en la dirección opuesta. La luz es distinta, los ángulos cambian, te fijas en cosas que a la ida habías ignorado. Es el mismo paseo pero no es la misma experiencia.
Los últimos diez minutos de vuelta son los mejores. El cuerpo está cansado pero de la forma buena, la que te hace sentir que has hecho algo. La cabeza está despejada. Los problemas que antes parecían enormes ahora tienen un tamaño manejable. No es que se hayan resuelto, es que les has quitado el drama.
## Por qué no voy más
Me hago esta pregunta cada vez que vuelvo. Si cada vez que voy me siento mejor, ¿por qué no voy todos los días? La respuesta es estúpida pero honesta: porque siempre hay algo "más urgente". Un correo que contestar, un deploy que hacer, un bug que arreglar. La urgencia le gana a lo importante casi siempre.
Pero lo importante sigue ahí cuando lo urgente se resuelve. El paseo sigue ahí. El mar sigue ahí. Los cuarenta y cinco minutos que necesito para limpiar la cabeza siguen siendo cuarenta y cinco minutos. No es mucho. Es mucho menos de lo que pierdo al día en cosas que no importan.
Así que si lees esto y tienes un sitio al que te gusta ir pero al que no vas lo suficiente, ve. Hoy. Ahora. Luego me cuentas.
---
# ClickHouse para desarrolladores que vienen de PostgreSQL
URL: https://javiervalencia.net/post/clickhouse-para-desarrolladores-que-vienen-de-postgresql
ClickHouse es una base de datos columnar diseñada para análisis de grandes volúmenes de datos. Si vienes de PostgreSQL, muchas cosas te van a resultar familiares porque ClickHouse habla SQL. Pero las diferencias internas son enormes y si las ignoras vas a sufrir. Este post explica ClickHouse desde la perspectiva de alguien que ya sabe PostgreSQL.
## Qué problema resuelve

PostgreSQL es una base de datos de propósito general optimizada para transacciones: inserta una fila, lee una fila, actualiza una fila. Es excelente para la operativa diaria de una aplicación.
ClickHouse resuelve un problema diferente: consultas analíticas sobre millones o miles de millones de filas. "¿Cuántas llamadas se hicieron ayer agrupadas por país y hora?" "¿Cuál es la tendencia de facturación de los últimos 36 meses?" "¿Qué páginas generan más tráfico los martes entre las 10 y las 14?"
En PostgreSQL, estas consultas sobre tablas con cientos de millones de filas tardan minutos u horas. En ClickHouse tardan segundos o fracciones de segundo. La diferencia no es de optimización: es de arquitectura.
## Columnar vs filas
PostgreSQL almacena los datos por filas. Cuando lees un registro, se lee toda la fila completa del disco: id, nombre, email, dirección, teléfono, fecha de registro, todo. Si solo necesitas el email, da igual: se lee todo.
ClickHouse almacena los datos por columnas. Cada columna es un fichero separado en disco. Si tu consulta solo necesita `country` y `duration`, solo lee esas dos columnas. Las otras ni las toca. En una tabla con 50 columnas y 100 millones de filas, la diferencia de IO es brutal.
Además, los datos de una misma columna se comprimen mucho mejor que los de una fila completa. Una columna de países tiene valores repetidos (España, España, Francia, España...) que se comprimen al 5-10% de su tamaño original. Una tabla de 100GB en PostgreSQL puede ocupar 10-15GB en ClickHouse.
## Lo que se mantiene

El SQL de ClickHouse es muy parecido al de PostgreSQL:
```sql
SELECT
country,
toStartOfHour(created_at) AS hour,
count() AS calls,
avg(duration) AS avg_duration
FROM calls
WHERE created_at >= '2026-04-01'
GROUP BY country, hour
ORDER BY calls DESC
LIMIT 20;
```
Si sabes SQL, sabes ClickHouse. Los `JOIN`, `GROUP BY`, `HAVING`, `ORDER BY`, subqueries, CTEs... todo funciona. Las funciones cambian de nombre (PostgreSQL usa `date_trunc`, ClickHouse usa `toStartOfHour`) pero el concepto es el mismo.
## Lo que cambia radicalmente
### No hay UPDATE ni DELETE eficientes
En PostgreSQL haces `UPDATE users SET name = 'Juan' WHERE id = 42` y funciona en milisegundos. En ClickHouse, `ALTER TABLE ... UPDATE` es una operación pesada que reescribe partes enteras de la tabla. No está diseñado para eso.
ClickHouse es append-only por diseño. Los datos se insertan y se consultan. Si necesitas "borrar" un dato, lo marcas como borrado y lo filtras en las consultas, o usas `ReplacingMergeTree` que elimina duplicados en background.
### Los inserts son en batch
En PostgreSQL insertas fila a fila sin problema. En ClickHouse, insertar fila a fila es un antipatrón que puede destrozar el rendimiento. ClickHouse espera batches de miles o cientos de miles de filas por insert.
```sql
-- Esto es correcto
INSERT INTO events (timestamp, type, data)
VALUES
('2026-04-08 10:00:00', 'click', '{"page": "/"}'),
('2026-04-08 10:00:01', 'click', '{"page": "/about"}'),
-- ... cientos de filas más
```
La regla general: inserta al menos una vez por segundo con al menos 1000 filas por batch. Si tienes eventos en tiempo real, acumúlalos en un buffer y haz flush periódico.
### MergeTree y la clave de ordenación
En PostgreSQL creas una tabla y luego añades índices. En ClickHouse, la clave de ordenación (`ORDER BY` en la definición de la tabla) es la decisión más importante que vas a tomar:
```sql
CREATE TABLE calls (
created_at DateTime,
country String,
customer_id UInt32,
duration UInt16
) ENGINE = MergeTree()
ORDER BY (country, created_at);
```
La clave de ordenación determina cómo se organizan los datos en disco. Las consultas que filtran por los primeros campos de la clave son rápidas. Las que filtran por campos que no están en la clave tienen que escanear más datos.
Piensa en la clave de ordenación como un índice que siempre está ahí y que define la estructura física de los datos. No puedes añadirlo después: tienes que elegirlo al crear la tabla.
## Cuándo usar ClickHouse

- **Logs y eventos**: millones de registros por día, consultas analíticas sobre periodos de tiempo.
- **Métricas y monitorización**: series temporales con agregaciones por minuto/hora/día.
- **Analytics de producto**: quién hace qué, cuándo, cuántas veces.
- **Facturación por uso**: calcular consumos sobre registros de llamadas, API calls, etc.
## Cuándo NO usar ClickHouse
- **Aplicaciones transaccionales**: si necesitas UPDATE y DELETE frecuentes, usa PostgreSQL.
- **Pocos datos**: si tu tabla tiene menos de un millón de filas, PostgreSQL es más que suficiente.
- **Consultas por clave primaria**: si tus consultas son "dame el usuario 42", PostgreSQL con un índice es instantáneo. ClickHouse no está optimizado para eso.
- **ACID estricto**: ClickHouse no garantiza transacciones en el sentido clásico.
## La combinación ganadora
En los proyectos donde uso ClickHouse, siempre va acompañado de PostgreSQL. PostgreSQL lleva la operativa: usuarios, configuración, estado de la aplicación. ClickHouse lleva el analytics: logs, CDRs, métricas, eventos.
La aplicación escribe en ambas bases de datos. Las consultas transaccionales van a PostgreSQL. Los dashboards, reportes y análisis van a ClickHouse. Cada base de datos hace lo que mejor sabe hacer.
No es más complejo de lo que parece. Es una conexión más en tu aplicación. Y la primera vez que una consulta que tardaba tres minutos en PostgreSQL se ejecuta en 200 milisegundos en ClickHouse, todo el esfuerzo merece la pena.
---
# webhooks.javiervalencia.net: testing de webhooks sin registros, sin servidores, sin excusas
URL: https://javiervalencia.net/post/webhooks-javiervalencia-net-testing-de-webhooks
Cualquiera que haya integrado [**Stripe**](https://docs.stripe.com/webhooks), [**GitHub**](https://docs.github.com/en/webhooks), [**GitLab**](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html), [**Mailgun**](https://documentation.mailgun.com/docs/mailgun/user-manual/tracking-messages/#webhooks), [**Twilio**](https://www.twilio.com/docs/usage/webhooks) o cualquier sistema moderno que hable webhooks se ha encontrado con el mismo problema: **necesitas una URL pública para recibir los eventos de prueba, pero estás desarrollando en local**. Y el sistema de webhooks del proveedor no va a llegar a tu `localhost:3000`.
Durante años, la solución ha sido `ngrok`, `localtunnel`, `cloudflared tunnel`, o montar un VPS con un Docker expuesto. Todos funcionan. Todos exigen instalar algo, registrarse, abrir puertos, gestionar túneles. Para algo que debería ser: dame una URL, mándale peticiones, muéstrame qué llega.
[`webhooks.javiervalencia.net`](https://webhooks.javiervalencia.net) hace exactamente eso, sin registro y sin binarios.
Este post cuenta qué es, cómo funciona y cómo lo uso yo en el día a día.
## Qué es
Dos herramientas en una:
**Modo Inbound (Recibir)**: generas una URL temporal, gente o sistemas externos mandan peticiones HTTP a esa URL, y tú ves cada petición aparecer en tiempo real en una página web. Cabeceras, cuerpo, método, query string: todo.
**Modo Outbound (Enviar)**: un formulario donde escribes un verbo HTTP, una URL, cabeceras y un cuerpo, le das a enviar y ves la respuesta completa. Útil para probar endpoints ajenos desde el navegador sin instalar nada.
No hay cuentas, no hay tokens, no hay cookies. Abres la página, trabajas, cierras.
## El flujo típico

Dibujemos el caso más frecuente: estoy integrando un webhook de **Stripe** en una aplicación en local, y quiero ver qué evento me manda Stripe cuando un cliente paga.
1. Abro [`webhooks.javiervalencia.net/inbound`](https://webhooks.javiervalencia.net/inbound) y pincho "Generar URL".
2. Me da una URL temporal del tipo `https://webhooks.javiervalencia.net/inbound/abc123xyz`.
3. En el panel de Stripe configuro esa URL como endpoint de webhooks de prueba.
4. Lanzo un pago de prueba desde el panel de Stripe.
5. En mi pestaña abierta de `webhooks.javiervalencia.net/inbound/abc123xyz`, veo aparecer la petición de Stripe en directo: cabeceras, firma, cuerpo JSON completo. Puedo inspeccionarlo.
6. Cuando tengo clara la estructura del payload, apunto el endpoint de Stripe a mi `localhost` (usando `ngrok` o similar) y paso al testing real contra mi código.
El paso 5 es lo que ahorra tiempo. Lo que antes significaba arrancar ngrok, cambiar la URL, recargar, mirar logs, aquí es una pestaña que ya tenías abierta.
## Ejemplos prácticos
### Recibir peticiones
Generas la URL y se la pasas a cualquier cosa que mande HTTP. Para probarlo a manopla, abres una terminal:
```bash
# La URL que te ha dado la web; copia la que te genere a ti
URL="https://webhooks.javiervalencia.net/inbound/abc123xyz"
curl -X POST "$URL" \
-H "Content-Type: application/json" \
-H "X-Custom-Header: prueba" \
-d '{"evento": "pago_completado", "importe": 42.50}'
```
Y en la página web, sin recargar, ves aparecer la petición con:
- Método: `POST`
- URL completa
- Cabeceras recibidas (las que mandaste más las que añade nginx: `X-Forwarded-For`, `X-Real-IP`, `X-Request-ID`, etc.)
- Body en texto plano
- Intento de parseo como JSON si el `Content-Type` lo permite
- Timestamp de recepción con precisión de milisegundos
Lo que aparece en el navegador llega por un stream [Server-Sent Events (SSE)](https://developer.mozilla.org/es/docs/Web/API/Server-sent_events), no por polling. Es instantáneo.
### Configurar la respuesta
Cuando un sistema externo te manda un webhook, espera una respuesta. Stripe, por ejemplo, reintenta si no recibe un `2xx` en 20 segundos. Para simular distintos escenarios, la página deja configurar:
- **Status code** de la respuesta (`200`, `201`, `400`, `500`, `503`...).
- **Cabeceras** de respuesta.
- **Body** de respuesta.
Así puedes simular "mi servidor ha fallado" y ver cómo se comporta el cliente que manda el webhook: ¿reintenta? ¿cuántas veces? ¿con qué backoff? Tremendamente útil para probar la lógica de reintentos de una integración.
### Enviar peticiones

El modo outbound es lo inverso. Una pantalla con:
- Un input de URL de destino.
- Un selector de método: `GET`, `POST`, `PUT`, `PATCH`, `DELETE` (los [métodos HTTP estándar](https://www.rfc-editor.org/rfc/rfc9110#name-methods)).
- Una sección de cabeceras con un desplegable de las más comunes (`Content-Type`, `Accept`, `Authorization`, `Cache-Control`, `User-Agent`, `X-API-Key`...) y un campo libre para las custom.
- Un body de texto (por defecto trae `{"status":"ok"}`).
- Un botón "Send Request".
Al enviar, se muestra la respuesta completa en dos vistas: **Formatted** (JSON bonito, HTML resaltado) y **Raw** (lo que llega por la red, tal cual).
¿Por qué usar esto en vez de [`curl`](https://curl.se/)? Dos motivos:
1. Es una URL compartible. "Oye, prueba este endpoint desde aquí" con un copy-paste del link y los campos precargados.
2. No todo el mundo tiene curl a mano o domina sus flags. Un formulario vale.
Yo suelo acabar volviendo a curl para iteraciones rápidas, pero cuando le mando a un colega un endpoint para que pruebe, le mando este.
### Probando un endpoint que requiere auth
```
URL: https://api.ejemplo.com/v1/usuarios/42
Método: GET
Cabeceras:
Authorization: Bearer eyJhbGc...
Accept: application/json
Body: (vacío)
```
Le das al botón, ves el JSON de respuesta, compruebas que tu token funciona y que la estructura es la esperada.
### Probando un webhook de salida contra ti mismo
```
URL: https://webhooks.javiervalencia.net/inbound/abc123xyz
Método: POST
Cabeceras:
Content-Type: application/json
Body:
{"from": "outbound", "to": "inbound", "ts": "2026-04-14T09:00:00Z"}
```
Es decir: puedes usar el modo outbound para mandarte a ti mismo una petición al endpoint inbound. Suena absurdo, pero es el test más rápido para confirmar que el servicio está vivo.
### Llamarlo por curl directamente
El endpoint outbound también es una API. Desde la CLI:
```bash
curl -X POST https://webhooks.javiervalencia.net/outbound \
-H "Content-Type: application/json" \
-d '{
"url": "https://httpbin.org/post",
"method": "POST",
"headers": {"Content-Type": "application/json"},
"body": "{\"hola\":\"mundo\"}"
}'
```
Y te devuelve la respuesta que dio `httpbin.org`. Útil para saltarse restricciones de CORS o probar endpoints desde otra IP de salida (no desde la tuya).
### Flujo completo: depurar un webhook de GitHub paso a paso
Escenario real que hice la semana pasada: un webhook de GitHub a una Lambda interna no estaba llegando, o llegaba mal. No tenía los logs de la Lambda delante y no quería perder una hora buscándolos. Pasos:
1. Genero una URL inbound en `webhooks.javiervalencia.net/inbound` y copio la cadena `abc123xyz`.
2. En el repo de GitHub, **Settings → Webhooks → Edit** del webhook existente. Cambio la URL de `https://lambda-proxy.interno/github` a `https://webhooks.javiervalencia.net/inbound/abc123xyz`. Content type sigue en `application/json`, secret lo mantengo.
3. En la página de GitHub del webhook, pincho **Recent Deliveries → Redeliver** en la última entrega fallida.
4. En mi pestaña de `webhooks.javiervalencia.net` aparece la petición en vivo.
5. Leo la cabecera `X-GitHub-Event: pull_request`, `X-GitHub-Delivery: ...`, `X-Hub-Signature-256: sha256=...`. Y el cuerpo completo.
6. Comparo lo que GitHub manda con lo que la Lambda espera. En mi caso, la Lambda esperaba `X-GitHub-Signature` (viejo) y GitHub manda `X-Hub-Signature-256` (nuevo). Bug encontrado en cinco minutos.
7. Restauro la URL original del webhook en GitHub.
El mismo flujo vale para cualquier webhook que puedas reenviar manualmente: Stripe tiene "Resend", GitLab tiene "Test", Mailgun tiene un botón equivalente. Cuando se puede disparar a voluntad, inspeccionar el payload es trivial con este servicio.
### Verificar la firma de un webhook
Cuando tienes la firma cruda delante, escribir el verificador es trivial. Ejemplo Go para la firma `X-Hub-Signature-256` de GitHub, que uso como chuleta:
```go
func verifyGitHubSignature(body []byte, sigHeader, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(sigHeader))
}
```
Con el payload y la firma que ves en la página del servicio, pruebas este snippet en un `main` suelto hasta que `Equal` devuelve `true`. Es la iteración más rápida que existe para depurar una verificación de HMAC.
## Tecnología por dentro

Es un servicio pequeño escrito en [Go](https://go.dev) con un objetivo explícito: no depender de nada más allá de la stdlib.
- **[`net/http` de Go](https://pkg.go.dev/net/http)**: el router y el servidor. Los path params del mux 1.22+ sirven para rutas como `/inbound/{id}`. No hay `chi`, `echo`, `gin` ni `fiber`.
- **[Server-Sent Events (SSE)](https://developer.mozilla.org/es/docs/Web/API/Server-sent_events)**: el mecanismo que empuja al navegador cada nueva petición recibida. Elegí SSE sobre [WebSockets](https://developer.mozilla.org/es/docs/Web/API/WebSockets_API) por tres razones: es HTTP plano (pasa por cualquier proxy sin problemas), es unidireccional (servidor → cliente, que es todo lo que necesito aquí), y la API del navegador ([`EventSource`](https://developer.mozilla.org/es/docs/Web/API/EventSource)) es trivial.
- **Vanilla JS**: la interfaz web no usa React, Vue, Svelte, ni ningún framework. Es HTML+CSS+JS a pelo, menos de 200 líneas totales. El stream se consume con `new EventSource(url)` y ya está.
- **Almacenamiento en memoria con expiración**: cada URL inbound tiene un buffer circular de las últimas peticiones recibidas, con TTL. No hay base de datos. Si el proceso cae, se pierde. Lo cual encaja con el modelo mental: esto es un scratch pad, no un sistema de registro.
- **TTL de sesión**: **una hora desde la última actividad**. Si dejas una URL creada y nadie la toca en una hora, el buffer se libera. Si la usas cada pocos minutos, la URL vive indefinidamente.
- **Límite de tamaño**: **10 KB por petición** (body). Suficiente para cualquier webhook de verdad (los payloads de [Stripe](https://docs.stripe.com/webhooks), [GitHub](https://docs.github.com/en/webhooks/webhook-events-and-payloads) y [Mailgun](https://documentation.mailgun.com/docs/mailgun/user-manual/tracking-messages/#webhooks) están muy por debajo), excesivo solo para cargas de archivos, que no son el caso de uso.
El código total ronda las 600 líneas de Go y 200 de JavaScript. Intencionadamente pequeño.
### Ejemplo del cliente SSE en el navegador
El JavaScript que consume el stream es prácticamente este:
```javascript
const id = location.pathname.split('/').pop();
const source = new EventSource(`/inbound/${id}/stream`);
source.addEventListener('request', (event) => {
const req = JSON.parse(event.data);
addRequestToUI(req);
});
source.addEventListener('error', () => {
console.warn('SSE reconectando...');
});
```
Y del lado servidor, un `http.Handler` estándar con los headers de SSE:
```go
func (h *Hub) Stream(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
id := r.PathValue("id")
ch := h.subscribe(id)
defer h.unsubscribe(id, ch)
for {
select {
case req := <-ch:
fmt.Fprintf(w, "event: request\ndata: %s\n\n", req.JSON())
w.(http.Flusher).Flush()
case <-r.Context().Done():
return
}
}
}
```
Es literalmente todo lo que hace falta. No hay librerías SSE; ni se necesitan.
## Casos de uso reales
Los que usa la gente normal:
**Integrar el webhook de Stripe**. Lo he descrito arriba. También vale para [**PayPal**](https://developer.paypal.com/api/rest/webhooks/), **Redsys**, **Paddle**, **Lemon Squeezy**: cualquier pasarela de pagos que notifique con webhooks.
**Depurar un webhook de GitHub**. Cuando algún `push`, `pull_request` o `release` dispara un evento que no llega bien a tu CI, apuntar el webhook a una URL del servicio te deja ver el payload sin tocar el CI. ¿El JSON de `pull_request.synchronize` tiene `pull_request.number` o `number`? Fácil: lo disparas y lo miras.
**Probar integraciones con Slack**. Los "slash commands" y los "Interactive Components" de [Slack](https://api.slack.com/interactivity/slash-commands) mandan un `POST` con `application/x-www-form-urlencoded`. Verlos antes de escribir el handler ahorra horas.
**Validar webhooks de email**. [Mailgun](https://www.mailgun.com/), [SendGrid](https://sendgrid.com/) y [Postmark](https://postmarkapp.com/) mandan eventos (`delivered`, `bounced`, `opened`, `clicked`). Conocer la forma exacta del payload desde una URL temporal evita leer tres docs a la vez.
**Ver qué manda un cliente al que estás dando soporte**. Esta es una de mis favoritas: un cliente se queja de que tu API le está rechazando sus peticiones. Le pides que apunte su código a una URL del servicio unos minutos. Miras lo que manda. Le dices qué está mal. Problema resuelto en diez minutos en lugar de dos días de ping-pong con logs.
**Reproducir un bug reportado por un cliente**. Le pides el JSON exacto que le falló. Lo pegas en el outbound, lo mandas a tu endpoint de staging, reproduces el bug localmente. Mucho más rápido que un hilo de email.
## Comparativa rápida con alternativas
- **[webhook.site](https://webhook.site)**: la alternativa clásica. Funciona bien, tiene más features (variables, scripting, replays...), pero mete un montón de JavaScript de tracking y los datos se guardan en sus servidores. Si eso te da igual, es más potente.
- **[RequestBin](https://requestbin.com)**: similar pero requiere cuenta Pipedream para la mayoría de features. Más orientado a automatización.
- **[`ngrok`](https://ngrok.com)**: otra categoría. Expone tu `localhost` al exterior con un túnel. Ideal cuando ya tienes el handler del webhook y quieres recibirlo en local. No sirve para la fase de "¿qué forma tiene este payload?".
- **[`cloudflared tunnel`](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)**: como ngrok pero gratis y de Cloudflare.
- **[Hookdeck](https://hookdeck.com/)**: herramienta comercial más seria, con replays, filtros, transformaciones. Para equipos que procesan millones de webhooks al día.
La regla que uso yo: `webhooks.javiervalencia.net` para la fase de exploración ("¿cómo es el payload? ¿funciona este endpoint?"), `ngrok` o `cloudflared` para la fase de desarrollo real ("quiero recibir el webhook en mi código en ejecución"), y una herramienta seria como Hookdeck para cuando eso se convierte en infraestructura de producción.
## Limitaciones
Seamos honestos:
- **10 KB de body**. Si necesitas subir ficheros grandes, no es el sitio.
- **Sin persistencia**. Cierras el navegador y listo; el buffer sigue una hora, pero si reinicio el proceso se pierde.
- **Sin replay**. A diferencia de webhook.site, aquí no puedes re-ejecutar una petición vieja contra otro endpoint.
- **Sin scripting**. No hay transformaciones, reglas, filtros. Se mira y se compara.
- **No tiene SLA**. Es mío, corre en mi servidor; si se cae, se cae. Úsalo para lo que es: pruebas puntuales.
- **Visibilidad pública por URL**. Cualquiera con la URL aleatoria puede ver lo que se ha enviado mientras la sesión esté viva. No pongas datos reales de producción (tarjetas, tokens válidos, datos personales) en las pruebas; manda siempre contra sandboxes o con datos falsos. La aleatoriedad de la URL da seguridad por oscuridad, que es suficiente para pruebas pero nada más.
Si necesitas algo con SLA, persistencia y replays, paga una cuenta de webhook.site o Hookdeck. Si lo que necesitas es "dame una URL, muéstrame el payload", aquí lo tienes.
## Conclusión
Las herramientas que me resultan útiles en el día a día tienen dos virtudes: están disponibles sin fricción (sin registro, sin instalación, sin configuración) y resuelven un problema concreto sin intentar resolver otros tres de paso. [`webhooks.javiervalencia.net`](https://webhooks.javiervalencia.net) intenta ser eso para el problema de explorar webhooks: una URL, un stream en tiempo real, un formulario para enviar peticiones, cero cuentas.
Si te ahorra aunque sean quince minutos la próxima vez que integres una pasarela de pagos, habrá merecido la pena. Si rompes alguno de los límites, ya sabes dónde ver el código y montarlo tú.
---
# Padre de tres hijas: lo que nadie te dice
URL: https://javiervalencia.net/post/padre-de-tres-hijas-lo-que-nadie-te-dice
Tengo tres hijas. Las dos mayores son independientes, viven su vida y vienen a comer los domingos cuando les apetece, que es menos de lo que me gustaría y más de lo que reconocerían. Penélope, la pequeña, todavía está en casa y es con la que paso más tiempo. La diferencia entre criar a la primera y criar a la tercera es un abismo, y no porque una sea más fácil que otra sino porque tú eres una persona completamente diferente.
## Con la primera lo haces todo mal

Con la primera hija eres un desastre disfrazado de responsabilidad. Lees libros sobre crianza. Esterilizas los biberones como si fueran material quirúrgico. Te levantas a las tres de la mañana para comprobar que respira. Cada fiebre es una urgencia. Cada llanto es un drama. Cada decisión parece definitiva e irreversible.
Lo que nadie te dice es que esa intensidad no es amor: es miedo. Tienes tanto miedo de hacerlo mal que sobrecompensas haciendo demasiado. Y el niño, que es mucho más resistente de lo que crees, sobrevive no gracias a tu hipervigilancia sino a pesar de ella.
Con la primera también cometes el error de pensar que hay una forma correcta de hacer las cosas. Que existe un manual y que si lo sigues todo saldrá bien. Spoiler: no hay manual. Hay sentido común, paciencia y una cantidad insana de improvisación.
## Con la segunda te relajas
Con la segunda hija descubres algo liberador: los niños no se rompen. Se caen y se levantan. Comen arena y no pasa nada. Duermen en cualquier sitio. No necesitan silencio absoluto para dormir ni comida triturada hasta los seis años.
Te relajas porque ya has visto la película una vez y sabes cómo acaba: bien. La primera sobrevivió a tu inexperiencia, así que la segunda se beneficia de tu relajación. Y curiosamente, esa relajación produce mejores resultados. Menos estrés para los padres es menos estrés para los hijos. Quién lo iba a decir.
Con la segunda también descubres la dinámica entre hermanas, que es un mundo aparte. Se quieren, se pelean, se adoran, se odian, a veces todo en la misma tarde. Y tú estás en medio intentando ser justo, que es imposible porque la justicia para una niña de seis años es un concepto completamente distinto al de una de tres.
## Con la tercera eres otra persona

Con Penélope he descubierto algo que no sabía con las mayores: que puedo disfrutar de la paternidad sin la presión de hacerlo perfecto. No digo que con las mayores no lo disfrutara, pero había tanto ruido de fondo, tanta preocupación, tanto "¿lo estaré haciendo bien?", que a veces se me olvidaba parar y simplemente estar.
Con la tercera paras. Porque ya sabes que el tiempo pasa rápido, mucho más rápido de lo que crees cuando estás cambiando pañales a las cuatro de la mañana. Las mayores eran bebés hace nada y ahora son adultas independientes que tienen sus propios problemas, sus propias decisiones, su propia vida. Y tú estás ahí pensando "¿cuándo ha pasado esto?".
Con Penélope intento estar presente de una forma que con las mayores no supe. No porque no quisiera sino porque no sabía que era importante. Pensaba que ser buen padre era proveer, proteger, educar. Y lo es. Pero también es sentarte en el suelo a jugar sin mirar el móvil. Es escuchar una historia que no tiene ningún sentido durante diez minutos sin interrumpir. Es estar ahí, simplemente estar, sin hacer nada productivo.
## Les haces pasar vergüenza y no sabes cuándo empezó
Mis dos hijas mayores pasan vergüenza conmigo. No sé exactamente cuándo empezó. Un día eras el héroe que lo sabía todo y al día siguiente eres el señor que cuenta chistes malos y baila raro. No hay transición. No hay aviso. Simplemente un día tu hija te dice "papá, por favor, no hagas eso" con una cara que indica que preferiría estar en cualquier otro lugar del planeta.
Lo curioso es que haces exactamente lo mismo que hacías cuando eras su héroe. Los mismos chistes. Los mismos bailes. La misma forma de hablar. Lo que ha cambiado no eres tú: es su percepción de ti. Y eso no hay forma de evitarlo. Es biología. Necesitan separarse de ti para construir su propia identidad, y la forma más fácil de hacerlo es decidir que eres insoportable.
Lo que nadie te dice es que duele. No mucho. No de forma dramática. Pero duele un poco cada vez que tu hija se aleja medio metro cuando vas andando juntos por la calle. Cuando deja de cogerte la mano. Cuando prefiere quedarse en casa a ir contigo a cualquier sitio.
Y lo que tampoco te dicen es que vuelven. No a cogerte la mano ni a pensar que eres un héroe, eso ya no. Pero vuelven a llamarte, a pedirte consejo, a querer pasar tiempo contigo. Tarda unos años, pero vuelven.
## Lo que importa de verdad

Después de criar a tres hijas he llegado a una conclusión muy simple: lo único que importa es el tiempo. No la calidad del tiempo, que es un concepto inventado por gente que no tiene tiempo. La cantidad. Estar ahí. Muchas horas. Muchos días. Muchos años.
Los niños no recuerdan los regalos caros. No recuerdan las vacaciones perfectas. Recuerdan las tardes en el sofá viendo dibujos. Recuerdan las cenas normales donde se contaban las cosas del colegio. Recuerdan que estabas ahí cuando llegaban a casa. Lo extraordinario no es lo que recuerdan: recuerdan lo ordinario.
Así que si estás leyendo esto y tienes hijos pequeños, mi único consejo es: estate. No hagas nada especial. No intentes ser el padre perfecto. No leas libros sobre crianza. Solo estate. El resto se soluciona solo.
Y si tus hijos ya son mayores y te hacen pasar vergüenza, enhorabuena. Significa que lo estás haciendo bien.
---
# ipinfo.javiervalencia.net: API de geolocalización
URL: https://javiervalencia.net/post/ipinfo-javiervalencia-net-api-de-geolocalizacion-ip
Hace unos meses que tengo rodando un pequeño servicio propio en [`ipinfo.javiervalencia.net`](https://ipinfo.javiervalencia.net). Es una API de geolocalización de IPs: le das una dirección y te devuelve ciudad, región, país, coordenadas, código postal y zona horaria. Sin registro, sin API key, sin formulario, sin cookies. La usas desde la terminal, desde un script, desde un dashboard o desde un navegador.
Este post cuenta qué hace, cómo está construida, y unos cuantos ejemplos prácticos para que te hagas una idea de cuándo puede serte útil.
## Por qué existe
Llevo años usando servicios de geolocalización de IPs para cosas muy mundanas:
- Saber desde dónde viene una petición sospechosa en el log de nginx.
- Calcular la zona horaria correcta en un cron que corre en varios servidores repartidos por datacenters.
- Validar en un script de provisioning que la IP pública del servidor está realmente en el país que esperaba.
- Mostrar el país en un pequeño dashboard de métricas.
- Depurar problemas de enrutamiento de CDN.
Durante mucho tiempo tiré de `ipinfo.io`, `ipapi.co` o `ip-api.com`. Todos funcionan, pero todos tienen las mismas pegas: rate limits agresivos para la capa gratuita, obligación de registrarse para cualquier uso serio, o bloqueos cuando se huele que estás haciendo algo automatizado. Y si encima quieres usarlo desde varias máquinas, acabas gestionando tokens.
El caso es que los datos de geolocalización que uso en el 95% de los casos son los de la base gratuita de [MaxMind GeoLite2](https://www.maxmind.com/en/geolite2/signup), que se actualiza semanalmente y es más que suficiente para los usos que he listado arriba. Así que en una tarde monté el servicio yo mismo.
## Qué hace, en concreto

La API tiene dos endpoints:
| Endpoint | Qué hace |
|----------|----------|
| `GET /me` | Devuelve la geolocalización de la IP que hace la petición |
| `GET /{ip}` | Devuelve la geolocalización de la IP indicada (IPv4 o IPv6) |
Los campos que devuelve son:
- `ip`: la IP consultada.
- `city`: ciudad.
- `region`: región administrativa (comunidad autónoma en España, estado en EE.UU.).
- `country`: nombre del país en inglés.
- `country_code`: código ISO 3166-1 alfa-2.
- `postal_code`: código postal.
- `latitude` y `longitude`: coordenadas WGS84.
- `timezone`: zona horaria en formato [IANA](https://www.iana.org/time-zones) (ej. `Europe/Madrid`).
- `continent`: nombre del continente.
Algunos campos pueden venir vacíos. La precisión a nivel de IP pública es desigual: MaxMind acierta el país casi siempre, la región a menudo, la ciudad con frecuencia y el código postal en ocasiones. Para IPs de servidores de proveedores como Google o Cloudflare los datos son más vagos y suelen apuntar a un punto genérico del país.
## Ejemplos prácticos
Lo mejor es verlo con ejemplos. Todos los que siguen son comandos reales que puedes copiar y pegar.
### Consultar tu propia IP
```bash
curl https://ipinfo.javiervalencia.net/me
```
Una respuesta típica, si estoy conectado desde casa:
```json
{
"ip": "147.135.214.123",
"city": "Fuengirola",
"region": "Andalucía",
"country": "Spain",
"country_code": "ES",
"postal_code": "29640",
"latitude": 36.5394,
"longitude": -4.6239,
"timezone": "Europe/Madrid",
"continent": "Europe"
}
```
### Consultar una IP concreta
```bash
curl https://ipinfo.javiervalencia.net/8.8.8.8
```
Respuesta real:
```json
{
"ip": "8.8.8.8",
"city": "",
"region": "",
"country": "United States",
"country_code": "US",
"postal_code": "",
"latitude": 37.751,
"longitude": -97.822,
"timezone": "America/Chicago",
"continent": "North America"
}
```
Los campos vacíos para la DNS de Google son típicos: MaxMind no tiene datos de ciudad para muchas IPs de infraestructura.
### IPv6 también funciona
```bash
curl https://ipinfo.javiervalencia.net/2001:4860:4860::8888
```
Devuelve el mismo tipo de respuesta para la versión IPv6 del DNS de Google. No hay diferencia de comportamiento entre v4 y v6, y los formatos de dirección de [RFC 4291](https://www.rfc-editor.org/rfc/rfc4291) se aceptan todos.
### Cambiar el formato de salida

Por defecto, la API responde en JSON si detecta un cliente programático (`curl`, `wget`, `requests`...) y en HTML si es un navegador. Pero puedes forzar el formato de tres maneras.
**Con la extensión en la URL:**
```bash
curl https://ipinfo.javiervalencia.net/8.8.8.8.xml
curl https://ipinfo.javiervalencia.net/8.8.8.8.yaml
curl https://ipinfo.javiervalencia.net/8.8.8.8.csv
curl https://ipinfo.javiervalencia.net/8.8.8.8.ini
curl https://ipinfo.javiervalencia.net/8.8.8.8.txt
```
**Con la cabecera `Accept`:**
```bash
curl -H "Accept: application/xml" https://ipinfo.javiervalencia.net/8.8.8.8
curl -H "Accept: application/yaml" https://ipinfo.javiervalencia.net/8.8.8.8
curl -H "Accept: text/csv" https://ipinfo.javiervalencia.net/8.8.8.8
```
La prioridad es: extensión en la URL > cabecera `Accept` > detección del User-Agent. Es la aplicación práctica de la [negociación de contenido](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Content_negotiation) de HTTP, definida en [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation).
El formato CSV es útil para tuberías que acaban en una hoja de cálculo o en un `awk`. El INI, para scripts viejos que no quieren depender de `jq`. El YAML, para pipelines de Ansible que ingieren configuración. El HTML es lo que ves si abres la URL en el navegador: una tabla formateada con un mapa estático.
### Usarlo en scripts
Un caso típico: un script de provisioning que quiere saber en qué país está el servidor antes de seguir.
```bash
#!/usr/bin/env bash
set -euo pipefail
COUNTRY=$(curl -fsS https://ipinfo.javiervalencia.net/me | jq -r '.country_code')
if [[ "$COUNTRY" != "ES" ]]; then
echo "Error: este script espera un servidor en España, detectado $COUNTRY" >&2
exit 1
fi
echo "Servidor en España, continuando..."
```
El `jq -r '.country_code'` extrae el código de país del JSON. Con `-fsS`, `curl` falla si el HTTP status no es 2xx, se queda callado en éxito y muestra errores en stderr. Es el combo que uso siempre en scripts.
### Configurar la zona horaria desde la IP pública
```bash
TZ=$(curl -fsS https://ipinfo.javiervalencia.net/me | jq -r '.timezone')
echo "$TZ" | sudo tee /etc/timezone > /dev/null
sudo timedatectl set-timezone "$TZ"
```
Esto configura la zona horaria del servidor a partir de su IP pública. Útil en provisioning automatizado cuando no sabes a priori en qué datacenter va a acabar la máquina: un droplet de Digital Ocean que aparece en Frankfurt, un VPS de OVH que aparece en Estrasburgo, una instancia de Hetzner en Helsinki.
### Mostrar la ubicación en el prompt de la shell
Uno que uso en el `.bashrc` de máquinas remotas para saber dónde demonios estoy cuando abro una sesión SSH:
```bash
server_loc() {
curl -fsS https://ipinfo.javiervalencia.net/me 2>/dev/null \
| jq -r '.city + ", " + .country_code'
}
PS1="[$(server_loc)] \u@\h:\w\$ "
```
No es rápido (añade una petición HTTP al abrir la shell), pero es útil la primera vez que entras a una máquina desconocida y no te acuerdas de en qué zona horaria vive.
### Desde Python
```python
import urllib.request
import json
with urllib.request.urlopen("https://ipinfo.javiervalencia.net/me") as r:
data = json.loads(r.read())
print(f"Conectado desde {data['city']}, {data['country']}")
```
Sin dependencias externas: `urllib` y `json` vienen con Python 3.x estándar.
### Desde Go
```go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Info struct {
IP string `json:"ip"`
City string `json:"city"`
Country string `json:"country"`
}
func main() {
resp, err := http.Get("https://ipinfo.javiervalencia.net/me")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var info Info
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
panic(err)
}
fmt.Printf("%s desde %s (%s)\n", info.IP, info.City, info.Country)
}
```
También sin dependencias: `net/http` y `encoding/json` son stdlib.
### Desde un pipeline de nginx y awk
Si tienes un `access.log` con IPs y quieres sacar los países de los últimos 100 visitantes únicos:
```bash
awk '{print $1}' /var/log/nginx/access.log \
| sort -u \
| tail -n 100 \
| while read ip; do
curl -fsS "https://ipinfo.javiervalencia.net/$ip" \
| jq -r "[\"$ip\", .country_code] | @csv"
done > visitors.csv
```
El rate limit te permite unas 30 peticiones por minuto, así que para lotes grandes conviene espaciar o usar `xargs -P` con cuidado.
### Geofencing sencillo en un script
Imagina que tienes un backup nocturno que solo debe ejecutarse si el servidor sigue estando físicamente en el continente que esperas. Un script de paranoia contra movidas raras:
```bash
#!/usr/bin/env bash
set -euo pipefail
EXPECTED_CONTINENT="Europe"
CONTINENT=$(curl -fsS https://ipinfo.javiervalencia.net/me | jq -r '.continent')
if [[ "$CONTINENT" != "$EXPECTED_CONTINENT" ]]; then
echo "Aborto: servidor detectado en $CONTINENT, esperaba $EXPECTED_CONTINENT" >&2
logger -t backup "anomaly: continent $CONTINENT"
exit 1
fi
rsync -a --delete /data/ backup@remote:/backups/
```
No es una medida de seguridad seria (la geolocalización por IP es manipulable con VPN), pero sí un detector barato de "alguien ha redirigido mi DNS o me ha movido el servidor sin avisarme".
### Convertir IPs a CSV para una hoja de cálculo
```bash
for ip in 1.1.1.1 8.8.8.8 9.9.9.9 208.67.222.222; do
curl -fsS "https://ipinfo.javiervalencia.net/$ip.csv" | tail -n +2
done > resolvers.csv
```
El `tail -n +2` salta la línea de cabeceras CSV en todas menos la primera, dejándote un CSV limpio listo para pegar en LibreOffice Calc o en Google Sheets.
### Validar una lista de IPs sospechosas
Si tienes un fichero `sospechosas.txt` con una IP por línea y quieres agruparlas por país:
```bash
while read ip; do
cc=$(curl -fsS "https://ipinfo.javiervalencia.net/$ip" 2>/dev/null | jq -r '.country_code // "?"')
echo "$cc $ip"
done < sospechosas.txt | sort | uniq -c | sort -rn
```
Sale un histograma por país de tus atacantes. Un patrón que he visto demasiadas veces es que el 80% de las IPs agresivas de un día concreto vienen de dos países; con ese dato, un bloqueo temporal por país en el firewall resuelve el día.
## Cómo está construida por dentro

El servicio es un único binario en [Go](https://go.dev), compilado estáticamente, que corre detrás de nginx. No tiene base de datos, no tiene cache externa, no depende de ningún otro proceso. Las piezas principales:
- **[Base de datos GeoLite2-City de MaxMind](https://www.maxmind.com/en/geolite2/signup)**: un fichero `.mmdb` de unos 70 MB que se descarga una vez a la semana vía cron. Es la fuente de los datos. MaxMind lo ofrece gratis con un registro mínimo, y su licencia permite usarlo incluso comercialmente. Al arrancar el servicio, el fichero se `mmap`ea en memoria para que las consultas sean de microsegundos.
- **[Librería `oschwald/geoip2-golang`](https://github.com/oschwald/geoip2-golang)**: wrapper en Go del formato MaxMind DB. Resuelve IPs a registros en tiempo constante usando el [árbol binario propio de MaxMind](https://maxmind.github.io/MaxMind-DB/). Maneja IPv4 e IPv6 con el mismo método.
- **[`net/http` de la stdlib de Go](https://pkg.go.dev/net/http)**: el router y el servidor HTTP. Desde Go 1.22 el mux de la stdlib soporta path variables (`/{ip}`) y verbos en el patrón (`GET /me`), con lo cual no necesito `chi`, `gin`, `echo` ni ninguna otra librería de routing externa.
- **Negociación de contenido manual**: un middleware pequeño que mira la extensión de la URL, la cabecera `Accept` y el User-Agent (para detectar navegadores), y elige el renderer adecuado. Cada formato (JSON, XML, YAML, CSV, INI, HTML, texto plano) tiene su función que escribe en el `http.ResponseWriter`. El XML sale con [`encoding/xml`](https://pkg.go.dev/encoding/xml) de la stdlib, el YAML con [`gopkg.in/yaml.v3`](https://pkg.go.dev/gopkg.in/yaml.v3), el CSV con [`encoding/csv`](https://pkg.go.dev/encoding/csv). Solo una dependencia externa.
- **Rate limiting basado en IP**: un `sync.Map` con un contador por IP que se reinicia por ventana temporal deslizante. Devuelve `429 Too Many Requests` cuando superas **30 peticiones por minuto**. Esto basta para parar scrapers tontos sin molestar a nadie que use el servicio de forma razonable.
- **Zona horaria**: la librería `time` de Go ya entiende el formato IANA, así que devolver `Europe/Madrid` como string es suficiente. Si alguna vez lo necesito localmente, `time.LoadLocation(data.Timezone)` me lo da en un segundo.
Todo esto cabe en menos de 500 líneas de código Go, incluyendo los tests. Un proyecto intencionadamente pequeño.
## Privacidad
El servicio **no guarda logs de consultas**. Nginx está configurado para no loguear las peticiones a estos endpoints, y el servicio Go tampoco persiste nada. Lo único que se registra son métricas agregadas: número total de peticiones por hora, número de rate limits aplicados. Nada que pueda atar una IP concreta a una consulta concreta.
Tampoco usa cookies ni JavaScript de tracking. La versión HTML para navegadores carga un mapa estático generado con [OpenStreetMap](https://www.openstreetmap.org/) sin embebidos de terceros.
Esto es deliberado: una de las razones por las que monté esto es que no me gustaba la política de datos de los servicios equivalentes comerciales.
## Cuándo no usarlo
Un par de advertencias honestas:
- **La GeoLite2 no es tan precisa como la versión comercial.** Si tu negocio depende de saber con precisión en qué ciudad está cada cliente (por ejemplo, para fraud detection serio), contrata la base completa de MaxMind o una de sus competidoras. Para el 95% de usos técnicos, la gratuita es suficiente.
- **Es un servicio personal, no tiene SLA.** Lo aloja un servidor mío. Si se cae, se cae. No pongas tu facturación por encima.
- **No hace reverse DNS ni ASN lookup.** Solo geolocalización. Si necesitas saber el AS de una IP, mira [Team Cymru IP-to-ASN](https://www.team-cymru.com/ip-asn-mapping) o la API de [ip-api.com](https://ip-api.com/).
- **No intenta detectar VPN, proxy o Tor.** Eso se haría con una base distinta ([IP2Location PX](https://www.ip2location.com/database/ip2proxy), [MaxMind Anonymous IP](https://www.maxmind.com/en/geoip-anonymous-ip-database)). Aquí no aplica.
## Troubleshooting común
Algunos tropiezos que me han reportado:
- **Devuelve el país pero no la ciudad**. Normal para IPs de infraestructura (Google, Cloudflare, AWS), VPNs comerciales y pools de operadoras móviles. MaxMind prioriza la precisión: si no está segura, deja el campo vacío antes que adivinar.
- **"Mi IP sale mal por 200 km"**. La GeoLite2 tiene un error típico de 30-60 km en España, mayor en zonas rurales. Es una base agregada, no un GPS. Para fraud detection que dependa de bloques pequeños, no la uses.
- **IPv6 desde móvil sale en otro país**. Algunas operadoras asignan prefijos IPv6 globalmente que MaxMind etiqueta en la sede central del operador, no en donde estás tú. Aquí no hay solución: es un problema de la asignación, no del servicio.
- **Recibo `429` constantemente**. Estás superando las 30 peticiones por minuto desde una misma IP. Espera un minuto y espacia las peticiones con `sleep 2` o usa `xargs -P1 -I{} curl ...` con una pausa controlada.
- **`curl: (6) Could not resolve host`**. Si tu red tiene DNS capado, el nombre `ipinfo.javiervalencia.net` no resuelve. Puedes probar por IP directa con `curl --resolve ipinfo.javiervalencia.net:443:147.135.214.123 https://ipinfo.javiervalencia.net/me`.
## Límites y consideraciones
- **30 peticiones por minuto** por IP. Suficiente para un humano o un script; insuficiente para scraping masivo.
- **Tamaño de respuesta**: unos 200 bytes en JSON. Ignorable en cualquier red moderna.
- **Latencia típica**: 5-15 ms desde Europa. El lookup en la GeoLite2 es inferior a 1 ms; el resto es red.
- **Soporta HTTP/2 y HTTP/3** vía nginx.
- **Sin CORS**. El servicio responde con `Access-Control-Allow-Origin: *`, así que puedes llamarlo desde un `fetch()` en cualquier frontend sin montar un proxy.
- **Sin autenticación**. Cualquiera puede consultar cualquier IP pública. Las IPs privadas (`10.0.0.0/8`, `192.168.0.0/16`, `172.16.0.0/12`, `127.0.0.0/8`) devuelven `422 Unprocessable Entity` con un mensaje explicando que no tienen geolocalización posible.
- **Siempre HTTPS**. El servidor redirige `80` a `443` y fuerza HSTS, así que si usas `http://` acabas igualmente en HTTPS con una ida y vuelta extra.
- **Versiones estables de la base**. Me guardo en cold storage la base MaxMind de cada semana durante un año, por si alguna consulta histórica necesita reproducirse con los datos vigentes en una fecha concreta. No está expuesto en el API, pero si alguna vez lo necesito, el dato está.
## Conclusión
Si necesitas geolocalizar IPs de forma ocasional, sin registrar nada ni gestionar tokens, y te basta con la precisión de MaxMind GeoLite2, [`ipinfo.javiervalencia.net`](https://ipinfo.javiervalencia.net) es una herramienta que funciona. Si necesitas algo más serio (mayor precisión, SLA, detección de VPN), ve directo al proveedor comercial correspondiente.
El código es corto por diseño. El servicio va a seguir ahí mientras lo siga usando yo, que es básicamente lo único que puedo prometer. Si te es útil, úsalo. Si rompes el rate limit, respira hondo y monta uno tú: la receta está en este post.
---
# Systemd: más allá del systemctl start
URL: https://javiervalencia.net/post/systemd-mas-alla-del-systemctl-start
La mayoría de desarrolladores usa systemd para tres cosas: `systemctl start`, `systemctl stop` y `systemctl restart`. Pero systemd es una herramienta enormemente potente que puede hacer la vida de un sysadmin mucho más fácil si le dedicas media hora a entender sus opciones. Este post cubre las funcionalidades que uso a diario y que van mucho más allá de arrancar y parar servicios.
## Hardening: que tu servicio no pueda hacer lo que no debe

La funcionalidad de systemd que más me gusta y que menos gente conoce es el hardening de servicios. Con unas pocas directivas puedes limitar lo que un servicio puede hacer en el sistema:
```ini
[Service]
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/app/data
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
```
`ProtectSystem=strict` monta todo el filesystem como solo lectura excepto los paths que declares en `ReadWritePaths`. Si tu aplicación tiene una vulnerabilidad que permite escribir ficheros, no podrá escribir fuera de `/opt/app/data`. `ProtectHome=true` hace que `/home` sea inaccesible. `PrivateTmp` le da al servicio su propio `/tmp` aislado.
Es defensa en profundidad: incluso si tu aplicación se compromete, el daño que puede hacer está contenido. No sustituye a un buen código, pero añade una capa de protección que no cuesta nada activar.
Para auditar el hardening de un servicio existente:
```bash
systemd-analyze security mi-servicio.service
```
Te da una puntuación y una lista de recomendaciones. Apunta al verde.
## Dependencias y orden de arranque
Systemd no arranca los servicios en orden secuencial como hacía SysVinit. Los arranca en paralelo, tan rápido como puede. Si tu servicio necesita que la red esté disponible antes de arrancar, tienes que decírselo:
```ini
[Unit]
After=network-online.target
Wants=network-online.target
```
`After` dice "arranca después de esto". `Wants` dice "intenta arrancar esto también, pero no falles si no puedes". Si necesitas que sea obligatorio, usa `Requires` en vez de `Wants`.
Para dependencias entre tus propios servicios:
```ini
[Unit]
After=postgresql.service
Requires=postgresql.service
```
Esto garantiza que PostgreSQL esté arrancado antes de que tu aplicación intente conectarse. Sin esta directiva, tu app puede arrancar antes que la base de datos y fallar con un "connection refused" que te hace perder media hora diagnosticando.
## Restart automático con backoff

La directiva `Restart` controla qué pasa cuando tu servicio se cae:
```ini
[Service]
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=300
StartLimitBurst=5
```
`Restart=on-failure` reinicia el servicio si sale con un código de error (pero no si se para con `systemctl stop`). `RestartSec=5` espera cinco segundos antes de reiniciar para no martillear un servicio que falla inmediatamente. `StartLimitBurst=5` y `StartLimitIntervalSec=300` limitan a cinco reintentos en cinco minutos para evitar bucles infinitos.
Para servicios críticos puedes usar `Restart=always`, pero úsalo con cuidado. Si tu servicio falla por una razón legítima (configuración incorrecta, puerto ocupado), reiniciarlo en bucle no va a solucionar nada y sí va a llenar los logs.
## Timers: el reemplazo de cron
Los timers de systemd son cron con esteroides. Son más legibles, tienen logging integrado y pueden depender de otros servicios:
```ini
# /etc/systemd/system/backup.timer
[Unit]
Description=Backup diario
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
```
```ini
# /etc/systemd/system/backup.service
[Unit]
Description=Ejecutar backup
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
```
`Persistent=true` es clave: si el servidor estaba apagado a las 3:00, ejecuta el backup al encender. Cron simplemente se lo salta.
Para listar los timers activos:
```bash
systemctl list-timers --all
```
## Journal: logs estructurados

`journalctl` es mucho más potente que `tail -f /var/log/syslog`:
```bash
# Logs de un servicio específico
journalctl -u mi-servicio
# Logs desde el último arranque
journalctl -u mi-servicio -b
# Logs de las últimas 2 horas
journalctl -u mi-servicio --since "2 hours ago"
# Seguir en tiempo real
journalctl -u mi-servicio -f
# Solo errores
journalctl -u mi-servicio -p err
# Formato JSON (para procesar con jq)
journalctl -u mi-servicio -o json | jq '.MESSAGE'
# Espacio usado por los logs
journalctl --disk-usage
# Limpiar logs de más de 7 días
journalctl --vacuum-time=7d
```
Lo que más me gusta del journal es que está integrado con systemd. No necesitas configurar logrotate por separado, no necesitas redirigir stdout a un fichero, no necesitas un servicio de logging aparte. Tu aplicación escribe a stdout y systemd se encarga del resto.
## Variables de entorno seguras
En vez de hardcodear configuración en el unit file, usa `EnvironmentFile`:
```ini
[Service]
EnvironmentFile=/opt/app/.env
```
El fichero `.env` contiene los secretos:
```
DATABASE_URL=postgres://user:pass@localhost/mydb
API_TOKEN=secreto
```
Importante: el fichero `.env` debe tener permisos restrictivos (`chmod 640`) y ser propiedad del usuario del servicio. Systemd lo lee al arrancar y lo pasa como variables de entorno al proceso.
No uses `Environment=` directamente en el unit file para secretos, porque el unit file suele estar en `/etc/systemd/system/` con permisos legibles para todos.
## Watchdog
El watchdog de systemd mata y reinicia tu servicio si deja de responder. Tu aplicación tiene que enviar un heartbeat periódico:
```ini
[Service]
Type=notify
WatchdogSec=30
```
En Go, puedes usar el paquete `go-systemd` para enviar el heartbeat:
```go
// Enviar heartbeat cada 15 segundos (la mitad de WatchdogSec)
go func() {
for range time.Tick(15 * time.Second) {
daemon.SdNotify(false, daemon.SdNotifyWatchdog)
}
}()
```
Si tu aplicación se queda bloqueada (deadlock, goroutine leak, consumo de memoria descontrolado), el watchdog lo detecta y reinicia el servicio. Es un seguro contra los fallos silenciosos que no producen un crash pero dejan la aplicación inservible.
## ExecStartPre y ExecStartPost
Puedes ejecutar comandos antes y después de arrancar el servicio:
```ini
[Service]
ExecStartPre=/usr/bin/test -f /opt/app/config.yaml
ExecStartPre=+/usr/local/go/bin/go build -o /opt/app/bin/server ./cmd/server
ExecStart=/opt/app/bin/server
ExecStartPost=/usr/bin/curl -s http://localhost:8080/health
```
El prefijo `+` ejecuta el comando como root, independientemente del `User` del servicio. Es útil para compilar binarios o crear directorios que necesitan permisos elevados.
Si algún `ExecStartPre` falla, el servicio no arranca. Esto es perfecto para validaciones: comprobar que un fichero de configuración existe, que un puerto está libre, o que una dependencia externa responde.
## Conclusión
Systemd tiene mala fama entre ciertos sectores de la comunidad Linux. Parte de esa mala fama es merecida: es complejo, monolítico y hace demasiadas cosas. Pero si lo aceptas como lo que es y aprendes a usarlo bien, es una herramienta extraordinariamente potente para gestionar servicios en producción.
El hardening por sí solo justifica aprender systemd en profundidad. La capacidad de decir "este servicio solo puede escribir en este directorio" con una línea de configuración es algo que antes requería contenedores, chroot o SELinux. Ahora es un `ProtectSystem=strict`.
---
# Vivir en la Costa del Sol sin ser turista
URL: https://javiervalencia.net/post/vivir-en-la-costa-del-sol-sin-ser-turista
Vivo en Mijas Costa, un pueblo de la Costa del Sol a medio camino entre Málaga y Marbella. Cuando le digo a alguien de fuera dónde vivo, la reacción siempre es la misma: "Qué suerte, vivir en la playa, con ese clima". Y tienen razón. Pero también se equivocan, porque vivir en un sitio turístico no es lo mismo que ir de vacaciones a un sitio turístico.
## El clima es real

Voy a empezar por lo bueno porque es lo que más pesa en la balanza: el clima. No es un mito ni una exageración publicitaria. En la Costa del Sol hay más de trescientos días de sol al año. En invierno la temperatura rara vez baja de diez grados. En un mal día de enero estás a catorce grados con sol y sin viento. Mientras en Madrid están a dos grados y en Bilbao llueve horizontalmente, aquí estás en manga corta tomando un café en una terraza.
Esto parece una tontería pero afecta a todo. A tu estado de ánimo, a tu salud, a cómo organizas el día. Puedes hacer planes al aire libre casi cualquier día del año sin mirar el parte meteorológico. Los niños juegan en la calle en febrero. Sales a caminar en diciembre sin abrigo. El sol te da una base de energía que no valoras hasta que pasas una semana en una ciudad gris.
## La temporada alta es un infierno
Dicho esto, de junio a septiembre la Costa del Sol se convierte en otra cosa. La población se multiplica. Las carreteras se atascan. Los restaurantes llenan y bajan la calidad porque total, los turistas vienen una semana y no van a volver a quejarse. Aparcar es un deporte de riesgo. La playa a las once de la mañana ya está imposible.
Los que vivimos aquí todo el año desarrollamos mecanismos de supervivencia. Aprendes a hacer la compra a primera hora. Evitas la AP-7 los sábados. Descubres playas y calas que los turistas no conocen. Vas a la playa a las siete de la tarde cuando la masa ya se ha ido. Y básicamente intentas vivir tu vida normal esquivando a dos millones de personas que han venido a hacer exactamente lo mismo que haces tú el resto del año.
No me malinterpretes: el turismo es el motor económico de esta zona y sin él no habría la mitad de servicios que tenemos. Pero vivirlo desde dentro es agotador. Cuando septiembre llega y la cosa se calma, hay un suspiro colectivo que se oye desde Nerja hasta Estepona.
## El coste de vida no es lo que crees

Hay una idea de que la Costa del Sol es cara. Y lo es si vives en primera línea de playa en Marbella o en Puerto Banús. Pero si te alejas dos kilómetros de la costa, los precios bajan considerablemente. Mijas Costa, Fuengirola, Benalmádena, Alhaurín... hay opciones para todos los bolsillos.
Eso sí, el mercado inmobiliario se ha vuelto absurdo. La combinación de teletrabajo post-pandemia, nómadas digitales y compradores extranjeros ha disparado los precios del alquiler hasta niveles que no tienen ningún sentido para los sueldos locales. Un piso de dos habitaciones que hace cinco años se alquilaba por seiscientos euros ahora no baja de mil. Y los sueldos no han subido ni de lejos al mismo ritmo.
Es la paradoja de vivir en un sitio deseable: cuanta más gente quiere vivir aquí, más difícil es vivir aquí.
## La comunidad internacional
Una cosa que me sorprendió cuando llegué es la cantidad de extranjeros que viven aquí de forma permanente. No turistas: residentes. Británicos, escandinavos, alemanes, holandeses... hay urbanizaciones enteras donde el español es el segundo idioma. Hay supermercados especializados en productos británicos. Hay bares donde solo se habla inglés.
Esto tiene su parte buena y su parte rara. La parte buena es que estás expuesto a culturas diferentes sin salir de tu pueblo. Mis hijas han crecido con compañeros de clase de diez nacionalidades distintas. La parte rara es que a veces te sientes extranjero en tu propio pueblo. Entras en un bar y el menú está en inglés. Le preguntas algo al camarero y te contesta en inglés. Hay zonas donde el comercio local funciona íntegramente en inglés porque su clientela es británica.
## Lo que nadie cuenta: la desconexión

Lo que más me costó al principio fue la sensación de desconexión. Cuando vives en un sitio donde todo el mundo parece estar de vacaciones, cuesta mantener el foco. El ambiente general es relajado, que está bien, pero a veces demasiado relajado. Si trabajas desde casa, como es mi caso, la tentación de "salir un rato que hace buen día" es constante.
También hay una desconexión cultural. Madrid, Barcelona, Bilbao, Valencia... son ciudades con una oferta cultural enorme. Aquí la oferta existe pero es más limitada. No vas a ver una exposición de primera línea en Fuengirola. Los conciertos grandes pasan de largo. El teatro es escaso. Málaga capital ha mejorado muchísimo en los últimos años, pero queda a cuarenta minutos.
## Entonces, ¿merece la pena?
Sí. Sin ninguna duda. Pero no por las razones que piensa la gente de fuera.
No merece la pena por la playa, que al final vas cuatro veces al año. No merece la pena por el chiringuito y las puestas de sol, que son bonitas pero no pagan las facturas. Merece la pena por el día a día. Por salir de casa un martes de noviembre y que haga sol. Por poder caminar por el paseo litoral de La Cala cualquier tarde del año. Por que tus hijos crezcan al aire libre. Por la calidad de vida en las cosas pequeñas que no salen en los folletos turísticos.
Vivir en la Costa del Sol no es vivir de vacaciones. Es vivir en un sitio donde las condiciones base son buenas y eso te da margen para todo lo demás. El clima no resuelve tus problemas, pero hace que sea más fácil enfrentarse a ellos. Y eso, aunque suene simple, marca una diferencia enorme.
---
# PostgreSQL: 10 consultas que todo desarrollador debería conocer
URL: https://javiervalencia.net/post/postgresql-10-consultas-que-todo-desarrollador-deberia-conocer
PostgreSQL es la base de datos que más uso y la que más respeto. Es potente, fiable y tiene funcionalidades que mucha gente no conoce porque se queda en el `SELECT * FROM` y poco más. Estas son diez consultas que uso regularmente y que creo que todo desarrollador debería tener en su repertorio.
## 1. CTEs (Common Table Expressions)

Las CTEs te permiten escribir subqueries con nombre, haciendo que las consultas complejas sean legibles:
```sql
WITH monthly_revenue AS (
SELECT
date_trunc('month', created_at) AS month,
SUM(amount) AS total
FROM invoices
WHERE status = 'paid'
GROUP BY 1
)
SELECT
month,
total,
total - LAG(total) OVER (ORDER BY month) AS diff_vs_previous
FROM monthly_revenue
ORDER BY month;
```
Sin la CTE, esto sería una subquery anidada ilegible. Con ella, primero calculas los ingresos mensuales y luego los comparas con el mes anterior. Cada paso tiene sentido por separado.
## 2. Window functions
Las window functions hacen cálculos sobre un conjunto de filas relacionadas sin agrupar los resultados. Son el salto cualitativo entre SQL básico y SQL de verdad:
```sql
SELECT
name,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_in_dept,
salary - AVG(salary) OVER (PARTITION BY department) AS diff_vs_avg
FROM employees;
```
Esto te da, para cada empleado, su ranking salarial dentro de su departamento y cuánto se desvía de la media. Sin window functions tendrías que hacer múltiples subqueries o joins consigo misma.
## 3. EXPLAIN ANALYZE

No es una consulta de datos sino una herramienta de diagnóstico. Si una consulta va lenta, `EXPLAIN ANALYZE` te dice exactamente por qué:
```sql
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders
WHERE customer_id = 42
AND created_at > '2026-01-01';
```
Lo importante es leer de abajo hacia arriba. Busca los nodos con mayor `actual time`, los `Seq Scan` donde esperabas un `Index Scan`, y los `rows=` que difieran mucho del `estimated=`. Esas discrepancias suelen indicar estadísticas desactualizadas o índices faltantes.
## 4. Índices parciales
Un índice parcial indexa solo las filas que cumplen una condición. Ocupa menos espacio y es más eficiente:
```sql
CREATE INDEX idx_orders_pending
ON orders (created_at)
WHERE status = 'pending';
```
Si el 95% de tus pedidos están completados y solo consultas los pendientes, este índice es mucho más pequeño y rápido que un índice completo sobre `status`. Es una de esas funcionalidades de PostgreSQL que no existen en MySQL y que cuando las descubres no puedes vivir sin ellas.
## 5. UPSERT (INSERT ... ON CONFLICT)

Insertar si no existe, actualizar si existe. Sin race conditions:
```sql
INSERT INTO page_views (url, count)
VALUES ('/post/mi-post', 1)
ON CONFLICT (url)
DO UPDATE SET count = page_views.count + 1;
```
Antes de que existiera `ON CONFLICT` había que hacer un `SELECT` + `INSERT` o `UPDATE` con lógica en la aplicación y rezar para que no hubiera una condición de carrera entre medias. Ahora es una operación atómica.
## 6. generate_series para rellenar huecos
Cuando haces un reporte por fechas, los días sin datos no aparecen. `generate_series` genera las fechas que faltan:
```sql
SELECT
d::date AS day,
COALESCE(COUNT(o.id), 0) AS orders
FROM generate_series(
'2026-01-01'::date,
'2026-01-31'::date,
'1 day'::interval
) AS d
LEFT JOIN orders o ON o.created_at::date = d::date
GROUP BY 1
ORDER BY 1;
```
Esto te da todos los días de enero con el número de pedidos, incluyendo ceros para los días sin actividad. Es fundamental para gráficas y reportes porque un hueco en los datos distorsiona la visualización.
## 7. Búsqueda de texto con trigrams
Para búsqueda aproximada sin montar un motor de búsqueda externo:
```sql
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_posts_title_trgm ON posts USING gin (title gin_trgm_ops);
SELECT title, similarity(title, 'posgresql') AS sim
FROM posts
WHERE title % 'posgresql'
ORDER BY sim DESC
LIMIT 10;
```
El operador `%` busca coincidencias por similitud. Nota que he escrito "posgresql" mal a propósito: la búsqueda por trigrams encuentra "PostgreSQL" igualmente. Es tolerante a errores tipográficos, que es exactamente lo que necesitas en un buscador.
## 8. LATERAL JOIN
Un `LATERAL JOIN` es como un bucle `for` en SQL: para cada fila de la tabla izquierda, ejecuta una subquery que puede referenciarla:
```sql
SELECT
c.name,
latest.amount,
latest.created_at
FROM customers c
CROSS JOIN LATERAL (
SELECT amount, created_at
FROM orders
WHERE customer_id = c.id
ORDER BY created_at DESC
LIMIT 3
) AS latest;
```
Esto te da los tres últimos pedidos de cada cliente. Sin `LATERAL` tendrías que usar window functions con `ROW_NUMBER()` y filtrar. Con `LATERAL` es directo y además PostgreSQL lo optimiza bien porque puede usar el índice de `customer_id` para cada cliente.
## 9. Estadísticas de la base de datos
PostgreSQL guarda estadísticas sobre sí misma. Estas vistas del sistema son oro para entender qué pasa en producción:
```sql
-- Tablas más grandes
SELECT relname, pg_size_pretty(pg_total_relation_size(oid)) AS size
FROM pg_class
WHERE relkind = 'r'
ORDER BY pg_total_relation_size(oid) DESC
LIMIT 10;
-- Índices sin usar
SELECT indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;
-- Cache hit ratio (debería ser >99%)
SELECT
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0) AS ratio
FROM pg_statio_user_tables;
```
Los índices sin usar ocupan espacio y ralentizan las escrituras. El cache hit ratio te dice si necesitas más RAM. Estas consultas deberían ejecutarse periódicamente en cualquier base de datos en producción.
## 10. LISTEN / NOTIFY
PostgreSQL tiene un sistema de pub/sub integrado. Sin colas externas, sin Redis, sin Kafka:
```sql
-- En una conexión
LISTEN new_orders;
-- En otra conexión
NOTIFY new_orders, '{"order_id": 42}';
```
La aplicación que escucha recibe el mensaje en tiempo real. Es perfecto para invalidar caches, disparar webhooks o actualizar dashboards sin polling. No reemplaza a un sistema de colas para volúmenes altos, pero para notificaciones internas entre componentes de la misma aplicación es más que suficiente.
## Conclusión
PostgreSQL tiene mucho más de lo que la mayoría de desarrolladores usa. Estas diez consultas cubren patrones que aparecen constantemente en aplicaciones reales: reportes, búsqueda, optimización, datos en tiempo real y operaciones atómicas.
Si solo te llevas una cosa de este post, que sea `EXPLAIN ANALYZE`. La capacidad de entender por qué una consulta va lenta es la habilidad más valiosa que puedes tener trabajando con bases de datos. Todo lo demás viene después.
---
# Por qué Cinema Paradiso es la mejor película jamás hecha
URL: https://javiervalencia.net/post/por-que-cinema-paradiso-es-la-mejor-pelicula-jamas-hecha
Hay películas que te gustan, películas que te emocionan y películas que te cambian. Cinema Paradiso es de las que te cambian. La vi por primera vez con veintitantos años, en un momento en el que no sabía muy bien qué hacer con mi vida, y algo encajó. No sabría explicar exactamente qué, pero desde entonces es mi película favorita y no ha habido nada que se le acerque.
## La historia más simple del mundo

Si lo reduces al argumento, Cinema Paradiso es la historia de un niño que se hace amigo de un viejo en un pueblo de Sicilia. El niño se llama Salvatore, el viejo se llama Alfredo y el punto de encuentro es el cine del pueblo, el Cinema Paradiso, donde Alfredo trabaja como proyeccionista.
No hay giros inesperados. No hay villanos. No hay efectos especiales. Es una película sobre un niño que crece, un viejo que envejece y un pueblo que cambia. Y con eso te destroza emocionalmente durante dos horas y cuarto.
La fuerza de Cinema Paradiso está precisamente en su simplicidad. No necesita complicarse porque lo que cuenta es universal: la infancia que se va, las personas que te forman, las decisiones que te alejan de donde vienes y la nostalgia de lo que dejaste atrás.
## Alfredo y la figura del mentor
Alfredo es uno de los mejores personajes de la historia del cine. Es el mentor que todos necesitamos y muy pocos tenemos: alguien que te quiere lo suficiente como para decirte la verdad aunque duela, y lo suficiente como para empujarte fuera del nido aunque eso signifique no volver a verte.
La escena en la que Alfredo le dice a Salvatore que se vaya del pueblo y no vuelva nunca es devastadora. "No quiero oírte hablar nunca más. No quiero que vuelvas. Olvídate de nosotros. Si descubres que te echas de menos, no cedas. Vuelve." No es crueldad. Es amor en su forma más pura y más difícil: querer lo mejor para alguien aunque lo mejor para él sea que se aleje de ti.
He pensado mucho en esa escena como padre. En algún momento mis hijas van a tener que irse, ya lo han hecho las mayores, y la tentación natural es retenerlas. Decirles que se queden cerca, que aquí están bien, que para qué arriesgarse. Pero Alfredo entiende algo que cuesta mucho entender: quedarse en el sitio cómodo es la forma más segura de no llegar a ser quien puedes ser.
## Los besos censurados

El cura del pueblo obliga a Alfredo a cortar las escenas de besos de todas las películas que proyecta. Los feligreses protestan, el público se queja, pero los besos desaparecen. Alfredo guarda todos los recortes en latas de película durante años.
Al final de la película, cuando Salvatore ya es un director de cine famoso y vuelve al pueblo para el funeral de Alfredo, le dejan como herencia esas latas. Salvatore las proyecta en una sala vacía y lo que ve es un montaje de todos los besos censurados durante décadas. Uno detrás de otro. Todos los besos que el pueblo no pudo ver.
Si no lloras con esa escena, comprueba que tienes pulso.
Lo que hace que funcione no es solo la emoción. Es lo que representa: toda la belleza que nos perdemos por culpa de la censura, del miedo, de la mojigatería, de las normas que nos imponemos o nos imponen. Todos esos besos existían. Alguien los filmó, alguien los actuó, alguien los sintió. Y un tipo con unas tijeras decidió que no eran apropiados.
Alfredo los guardó todos. No los tiró. Los guardó. Sabía que algún día alguien los necesitaría.
## La música de Morricone
No se puede hablar de Cinema Paradiso sin hablar de Ennio Morricone. La banda sonora de esta película es posiblemente la más hermosa jamás compuesta para cine. El tema principal te destroza con cuatro notas. No necesita más.
Morricone tenía la capacidad de traducir emociones a música de una forma que parece imposible. El tema de Cinema Paradiso suena a infancia perdida, a tardes de verano, a la luz de un proyector en una sala oscura, a todo lo que fuiste y ya no eres. Es nostalgia en estado puro convertida en melodía.
Hay compositores que hacen bandas sonoras espectaculares. Hans Zimmer te pone los pelos de punta. John Williams te hace sentir heroico. Pero Morricone te hace sentir humano. Y eso es mucho más difícil.
## El pueblo como personaje

Giancaldo, el pueblo ficticio de Sicilia donde transcurre la película, es un personaje más. El pueblo cambia con los años igual que cambian Salvatore y Alfredo. Al principio es un lugar vivo, ruidoso, lleno de gente que se junta en la plaza para ir al cine. Al final es un lugar que ha envejecido, que se ha vaciado, donde el cine se ha cerrado y la plaza ya no tiene el mismo significado.
Es lo que pasa con los pueblos de verdad. Yo no crecí en un pueblo siciliano, pero cualquiera que haya vuelto a un sitio después de años sabe de qué habla Tornatore. Las calles son las mismas pero todo es diferente. Los bares han cambiado de nombre, las tiendas han cerrado, la gente que conocías se ha ido o ha envejecido hasta ser irreconocible. El sitio sigue ahí pero el lugar que tú recuerdas ya no existe. Solo existe en tu memoria.
## Por qué la sigo viendo
Cada vez que veo Cinema Paradiso lloro en sitios diferentes. La primera vez lloré con los besos censurados. La segunda con la despedida de Alfredo. La tercera con el momento en que Salvatore vuelve al pueblo y ve el cine demolido. Cada etapa de mi vida hace que una parte diferente de la película me golpee más fuerte.
Supongo que eso es lo que define a una obra maestra: que crece contigo. Que no se agota. Que cada vez que vuelves a ella encuentras algo nuevo, no porque la película haya cambiado sino porque tú has cambiado.
Cinema Paradiso es mi película favorita porque me recuerda que las cosas importantes de la vida son simples: las personas que te quieren, los lugares donde fuiste feliz y el valor de dejarlo todo atrás para convertirte en quien tienes que ser. No necesitas más argumento que ese. Tornatore no lo necesitó. Y creó la mejor película jamás hecha.
---
# Cómo migré de WordPress a un blog en Go
URL: https://javiervalencia.net/post/como-migre-de-wordpress-a-un-blog-en-go
Durante años este blog corrió sobre WordPress. Funcionaba. Hacía lo que tenía que hacer. Pero con el tiempo la relación se fue desgastando hasta que un día decidí que era suficiente y lo tiré todo para empezar de cero. Este post cuenta por qué, cómo y qué he aprendido en el proceso.
## Por qué dejé WordPress

WordPress es un buen software. Lo digo sin ironía. Ha democratizado la publicación web de una forma que ninguna otra herramienta ha conseguido. Pero para un blog personal con diez posts, WordPress es como usar un camión articulado para ir a comprar el pan.
Mi instalación tenía PHP 8.4, MySQL, Nginx con FastCGI cache, un tema que había modificado hasta dejarlo irreconocible, seis plugins de los cuales solo entendía tres, y un panel de administración con más opciones que un avión comercial. Todo eso para servir texto con alguna imagen.
Los problemas concretos que me empujaron a salir:
**Rendimiento**: incluso con FastCGI cache, la primera visita después de un cache miss ejecutaba PHP, consultaba MySQL, cargaba plugins, evaluaba el tema y generaba HTML. Para un post estático. Absurdo.
**Seguridad**: WordPress es el CMS más atacado del mundo porque es el más usado. Cada semana hay una actualización de seguridad de algún plugin. Si no actualizas, estás expuesto. Si actualizas, puede romperse algo. Es un ciclo agotador.
**Complejidad innecesaria**: no necesito un editor visual. No necesito taxonomías avanzadas. No necesito un sistema de usuarios con roles. No necesito WooCommerce, Yoast SEO ni Jetpack. Necesito escribir Markdown, convertirlo a HTML y servirlo.
## Las alternativas que descarté
Antes de ponerme a programar, evalué las opciones existentes.
**Hugo**: el candidato más obvio. Generador estático en Go, rápido, potente, bien documentado. Lo usé durante una semana y me gustó la filosofía pero no el workflow. Tener que compilar el sitio cada vez que publicas un post y subir los ficheros generados añade fricción. Quería algo que leyera el Markdown directamente y lo sirviera.
**Jekyll, Eleventy, Astro**: misma categoría que Hugo. Generadores estáticos con paso de compilación. Descartados por la misma razón.
**Ghost**: elegante, moderno, bien diseñado. Pero es Node.js, requiere MySQL o SQLite, y tiene su propio ecosistema de temas. Demasiada infraestructura para lo que necesito.
**Escribir HTML a mano**: lo consideré durante cinco minutos. Luego me acordé de que quiero escribir posts, no luchar con etiquetas ``.
## La decisión: Go sin nada

Lo que quería era simple:
1. Escribir posts en Markdown con front matter YAML, como en Hugo.
2. Que el servidor los lea al arrancar, los compile a HTML y los sirva desde memoria.
3. Que si edito un fichero, se actualice automáticamente sin reiniciar.
4. Sin base de datos. Sin frameworks frontend. Sin paso de compilación.
Go es perfecto para esto. Un binario único que arranca en milisegundos, consume poca memoria, maneja concurrencia de forma nativa y tiene una librería HTTP en la stdlib que no necesita nada externo.
## La arquitectura
El servidor tiene tres componentes principales:
**Loader**: al arrancar, recorre `content/posts/` y `content/pages/`, lee cada `.md`, parsea el front matter con `adrg/frontmatter`, convierte el cuerpo a HTML con `goldmark` y lo guarda en memoria en un `Store` protegido con `sync.RWMutex`.
**Watcher**: un file watcher con `fsnotify` que vigila el directorio de contenido. Cuando detecta un cambio, recarga solo el fichero afectado. Si creas un fichero nuevo, aparece en el blog. Si lo borras, desaparece. Sin reiniciar nada.
**Server**: el mux de Go 1.22+ con rutas como `GET /post/{slug}` y `GET /{slug}`. Sin router externo. Los handlers leen del Store, construyen los datos del template y renderizan con `html/template`.
El flujo completo de una petición es: llega la request, el handler busca el post en el Store (una lectura de mapa con `RLock`, nanosegundos), construye los datos del template y renderiza. No hay IO de disco ni consultas a base de datos. Todo está en memoria.
## Lo que aprendí

**No necesitas tanto como crees.** Mi blog anterior tenía MySQL, PHP, un tema con diez ficheros, seis plugins y una configuración de Nginx de cien líneas. El nuevo tiene un binario de Go, unos templates HTML y ficheros Markdown. Hace exactamente lo mismo, más rápido, con menos superficie de ataque y con un coste de mantenimiento cercano a cero.
**Goldmark es excelente.** Es el parser de Markdown estándar en Go, el mismo que usa Hugo. Soporta GFM, HTML embebido y se extiende fácilmente. Con la extensión de chroma para syntax highlighting, los bloques de código se renderizan en el servidor sin enviar JavaScript al navegador.
**`fsnotify` simplifica todo.** El file watcher fue la parte que más me preocupaba y resultó ser la más sencilla. `fsnotify` es un wrapper sobre inotify que funciona sin sorpresas. Con un debounce de 100ms para evitar eventos duplicados y un reload selectivo por fichero, el sistema es reactivo sin ser frágil.
**El mux de Go 1.22+ es suficiente.** Antes necesitabas `gorilla/mux` o `chi` para tener rutas con parámetros. Ahora la stdlib soporta `{slug}`, verbos HTTP en el patrón y precedencia por especificidad. Para un blog no necesitas más.
## El despliegue
El blog corre como un servicio de systemd en un servidor Debian detrás de Cloudflare. Nginx hace de reverse proxy y Cloudflare gestiona el SSL del edge y la cache de estáticos. El binario arranca en menos de un segundo, consume unos 20MB de RAM y sirve las páginas en microsegundos.
Para publicar un post, escribo el `.md` en local, lo subo por SCP o por el API, y el watcher o el endpoint de reload se encarga del resto. Sin pipelines de CI/CD, sin deploys de cinco minutos, sin Docker. Un `scp` y listo.
## ¿Lo recomiendo?
Construir tu propio blog es un ejercicio interesante pero no es para todo el mundo. Si quieres escribir y publicar sin pensar en tecnología, WordPress sigue siendo la mejor opción. Si quieres un sitio estático sin complicaciones, Hugo es fantástico.
Pero si eres desarrollador, si te gusta entender cómo funcionan las cosas por dentro y si tienes un par de fines de semana libres, construir tu propio blog es una de las mejores formas de aprender. Tocas HTTP, templates, parsing, file watching, concurrencia, despliegue y rendimiento. Todo en un proyecto real que vas a usar todos los días.
Y lo mejor de todo: cuando algo no funciona, sabes exactamente dónde mirar. Porque lo has escrito tú.
---
# Lecciones de Breaking Bad aplicadas a la vida real
URL: https://javiervalencia.net/post/lecciones-de-breaking-bad-aplicadas-a-la-vida-real
He visto Breaking Bad tres veces. La primera por curiosidad, la segunda porque no me creía lo buena que era, y la tercera para confirmar que no hay nada mejor en televisión. Cada vez que la reveo descubro cosas nuevas, no tanto en la trama sino en lo que dice sobre las decisiones que tomamos y cómo nos contamos mentiras para justificarlas.
Esto va con spoilers de principio a fin. Si no la has visto, deja de leer y ve a verla. En serio. Esto puede esperar.
## El orgullo es más peligroso que la necesidad

Walter White empieza cocinando metanfetamina porque tiene cáncer, no tiene dinero y quiere dejar algo a su familia. Esa es la versión que se cuenta a sí mismo. Pero la verdad es que Walter White empieza a cocinar porque le humillaron. Porque dejó Gray Matter, la empresa que cofundó, y vio cómo otros se hicieron multimillonarios con su trabajo mientras él daba clases de química a adolescentes que no le escuchaban.
La necesidad económica es la excusa. El motor real es el orgullo herido. Y eso es algo que veo constantemente en la vida real: gente que toma decisiones terribles no porque no tenga alternativa, sino porque su ego no soporta la alternativa que tiene. Aceptar ayuda de Gretchen y Elliott habría resuelto el problema en el episodio dos. Pero Walter prefiere cocinar droga antes que admitir que necesita a alguien.
En el trabajo pasa igual, a otra escala. He visto proyectos fracasar porque alguien prefería hundirse con su decisión antes que reconocer que se había equivocado. He visto a gente rechazar ofertas de ayuda por puro orgullo. El ego mata más proyectos que la incompetencia.
## Las decisiones pequeñas son las que importan
Nadie se convierte en Heisenberg de un día para otro. No hay un momento en el que Walter White se siente y dice "voy a ser un narcotraficante". Lo que hay es una sucesión de decisiones pequeñas, cada una ligeramente peor que la anterior, cada una justificada por la anterior.
Primero cocina una vez. Luego otra. Luego necesita distribuir. Luego necesita protegerse. Luego necesita eliminar a la competencia. Cada paso parece lógico si miras solo el paso anterior. Pero si miras la foto completa, es una caída libre.
Esto aplica a todo. Nadie acaba quemado en un trabajo de repente. Es una sucesión de "solo esta vez me quedo hasta tarde", "solo este fin de semana trabajo", "solo este mes sin vacaciones". Cada decisión pequeña parece razonable. El problema es que se acumulan y cuando quieres darte cuenta llevas dos años sin descansar y no sabes cómo has llegado ahí.
La lección es vigilar las decisiones pequeñas, no las grandes. Las grandes las piensas. Las pequeñas las tomas en piloto automático.
## "I am the one who knocks"

Hay un momento en la serie en el que Walter deja de mentirse. Cuando Skyler le pregunta si está en peligro, él responde: "I am the one who knocks". Ya no es un profesor de química metido en problemas. Es Heisenberg. Y lo sabe. Y le gusta.
Todos tenemos algo de eso. No cocinamos metanfetamina, pero todos tenemos una versión de nosotros mismos que sabe que está haciendo algo mal y en el fondo lo disfruta. El compañero que disfruta siendo imprescindible porque nadie más entiende su código. El jefe que disfruta del control aunque diga que quiere delegar. La persona que disfruta del drama aunque diga que quiere paz.
La honestidad con uno mismo es la habilidad más difícil de desarrollar. Walter White tarda cinco temporadas en admitir que lo hacía por él mismo. "I did it for me. I liked it. I was good at it." Esa confesión final a Skyler es el momento más honesto de toda la serie. Y llega demasiado tarde.
## Nadie es solo una cosa
Lo que hace que Breaking Bad sea la mejor serie jamás hecha es que no hay personajes planos. Walter es un genio y un monstruo. Jesse es un yonqui y la brújula moral de la serie. Hank es un bocazas insoportable y el tipo más íntegro de todo el reparto. Skyler es la persona más racional de la familia y por eso la odian los espectadores, porque nadie quiere que le arruinen la fantasía.
En la vida real tendemos a clasificar a la gente en buenos y malos, competentes e incompetentes, amigos y enemigos. Pero la realidad es que todo el mundo es Walter White a pequeña escala: capaz de lo mejor y de lo peor, a veces en el mismo día. Entender eso no justifica el mal comportamiento, pero sí ayuda a no sorprenderse cuando alguien que admiras te decepciona, o cuando alguien que despreciabas te salva el día.
## El punto de no retorno existe, pero no sabes cuándo lo cruzas

En la serie hay un momento claro en el que todo se vuelve irreversible: cuando Walter deja morir a Jane. Antes de eso, podía parar. Después, ya no. Pero Walter no lo sabe en ese momento. Cree que puede seguir controlando la situación. Siempre cree que puede controlar la situación.
En la vida real el punto de no retorno también existe, pero rara vez lo identificas cuando lo cruzas. Lo reconoces meses o años después, mirando atrás. "Ahí fue donde todo cambió." Puede ser una conversación que no tuviste, una decisión que pospusiste, un momento en el que miraste para otro lado.
No tengo una solución para esto. No creo que la haya. Pero sí creo que ser consciente de que existe ayuda a prestar más atención. A no dar las cosas por sentado. A tener las conversaciones difíciles cuando toca y no cuando ya es tarde.
## La serie como espejo
Breaking Bad funciona porque no es sobre drogas ni sobre crimen. Es sobre lo que pasa cuando una persona inteligente se convence de que las reglas no aplican a él. Y eso no es ficción. Eso pasa todos los días, en todas partes, a todas las escalas.
Cada vez que la reveo me descubro identificándome con personajes diferentes. La primera vez era Walter: el incomprendido que por fin demuestra lo que vale. La segunda vez era Jesse: el tipo que intenta hacer lo correcto pero está rodeado de gente que no le deja. La tercera vez era Skyler: la persona que ve venir el desastre y nadie le hace caso.
Supongo que eso dice más de mí que de la serie. Pero ese es el punto. Las buenas historias no te cuentan cosas sobre los personajes. Te cuentan cosas sobre ti.
---
# Guía completa de CLAUDE.md: cómo dar contexto a Claude Code sobre tu proyecto
URL: https://javiervalencia.net/post/guia-completa-claude-md
Si usas Claude Code para trabajar en tus proyectos, hay un fichero que marca la diferencia entre tener que explicar lo mismo en cada sesión o que Claude entienda tu proyecto desde el primer momento: **CLAUDE.md**.
## Qué es CLAUDE.md

CLAUDE.md es un fichero Markdown que se coloca en la raíz de tu proyecto (o en otros lugares, como veremos) y que Claude Code lee automáticamente al inicio de cada sesión. Funciona como un manual de instrucciones persistente: le dice a Claude cómo está organizado tu proyecto, qué comandos usar, qué convenciones seguir y qué errores evitar.
Piensa en él como el onboarding que le harías a un compañero nuevo, pero escrito una sola vez y reutilizado en cada conversación.
## Por qué es importante
Sin CLAUDE.md, cada sesión con Claude Code empieza de cero. Le tienes que explicar que usas Go, que los tests se lanzan con `go test ./...`, que los errores se wrappean con contexto, que el proyecto no tiene base de datos... Con CLAUDE.md, todo eso ya está cargado en contexto antes de que escribas tu primera pregunta.
Las ventajas concretas:
- **Persistencia**: las instrucciones sobreviven entre sesiones.
- **Consistencia**: todo el equipo comparte las mismas reglas si el fichero está en el repositorio.
- **Ahorro de tokens**: no repites las mismas explicaciones una y otra vez.
- **Mejor calidad de respuestas**: Claude toma decisiones más acertadas cuando conoce el contexto.
## Dónde colocarlo

CLAUDE.md puede vivir en varios sitios, cada uno con un alcance diferente. Los más específicos tienen precedencia sobre los más generales:
| Alcance | Ubicación | Se comparte |
|---------|-----------|-------------|
| **Proyecto** | `./CLAUDE.md` o `./.claude/CLAUDE.md` | Sí, vía Git |
| **Personal por proyecto** | `./CLAUDE.local.md` | No (añadir a `.gitignore`) |
| **Personal global** | `~/.claude/CLAUDE.md` | No, aplica a todos tus proyectos |
| **Organización** | `/etc/claude-code/CLAUDE.md` (Linux) | Sí, gestionado por IT |
Cuando arranca una sesión, Claude recorre el árbol de directorios hacia arriba buscando ficheros CLAUDE.md y los concatena todos. Los de subdirectorios se cargan bajo demanda cuando Claude lee ficheros en esas carpetas.
`CLAUDE.local.md` es para preferencias personales que no quieres commitear: URLs de sandbox, datos de prueba, atajos de tu workflow.
## Qué incluir
### Sí incluir
- **Comandos de build, test y despliegue**: los que Claude no puede adivinar.
- **Convenciones de código** que se desvíen del estándar del lenguaje.
- **Instrucciones de testing**: frameworks, cómo lanzar tests, qué evitar.
- **Arquitectura del proyecto**: decisiones que no son evidentes leyendo el código.
- **Variables de entorno necesarias** (sin sus valores reales).
- **Gotchas**: cosas no obvias que pueden hacer perder el tiempo.
### No incluir
- **Secretos**: ni API keys, ni tokens, ni contraseñas. Nunca.
- **Convenciones estándar** que Claude ya conoce (no necesitas decirle que en Go las funciones exportadas empiezan en mayúscula).
- **Documentación extensa**: mejor enlazar a los docs.
- **Descripciones fichero a fichero**: Claude puede leer el código.
- **Información que cambia constantemente**: se queda desactualizada y confunde.
## Estructura recomendada

Un buen CLAUDE.md no debería superar las 200 líneas. Si se pasa, pierde efectividad porque diluye la atención de Claude. Aquí va una estructura que funciona:
```markdown
# CLAUDE.md
Instrucciones para Claude Code al trabajar en este proyecto.
## Sobre el proyecto
Descripción breve: qué es, qué stack usa, qué problema resuelve.
## Comandos frecuentes
- Build: `go build ./cmd/server`
- Tests: `go test ./...`
- Lint: `golangci-lint run`
- Dev: `air`
## Dependencias
Tabla o lista de las dependencias principales y para qué sirve cada una.
Solo las que no sean evidentes.
## Convenciones de código
- Idioma del código: inglés.
- Idioma de commits: español.
- Usar `slog` para logging.
- Errores: siempre hacer wrap con contexto.
## Arquitectura
Las decisiones importantes que Claude necesita conocer:
- Sin base de datos, los posts son ficheros Markdown.
- File watcher con fsnotify para recarga en caliente.
- Patrón handler -> service -> content loader.
## Estructura de directorios
Solo si no es la estándar del framework/lenguaje.
## Cosas a evitar
Lo que NO hay que hacer. Esto es especialmente útil:
- No usar `panic` fuera de `init()`.
- No añadir dependencias sin justificación.
- No hacer commits directos a main.
```
## Sé específico
La diferencia entre un CLAUDE.md útil y uno inútil está en la especificidad:
| Malo | Bueno |
|------|-------|
| Formatea el código correctamente | Usa tabs de 4 espacios, no 2 |
| Testea tus cambios | Ejecuta `go test ./...` antes de commitear |
| Mantén los ficheros organizados | Los handlers van en `internal/handler/`, un fichero por dominio |
| Maneja los errores | Usa `fmt.Errorf("contexto: %w", err)`, nunca `log.Fatal` |
## Funciones avanzadas
### Importar otros ficheros con `@`
Puedes referenciar ficheros externos para no duplicar información:
```markdown
Consulta @README.md para la visión general del proyecto.
Consulta @docs/api.md para la documentación del API.
```
### Directorio `.claude/rules/`
Para proyectos grandes, puedes organizar las instrucciones en ficheros separados por tema:
```
.claude/
├── CLAUDE.md
└── rules/
├── code-style.md
├── testing.md
└── security.md
```
Cada fichero se carga automáticamente. Además, puedes limitar reglas a ficheros concretos con frontmatter YAML:
```markdown
---
paths:
- "internal/handler/**/*.go"
---
# Reglas para handlers
- Los handlers devuelven error, no llaman a http.Error directamente.
- Usar AppError para errores con código HTTP.
```
### Generar un CLAUDE.md inicial con `/init`
Si partes de cero, ejecuta `/init` en la raíz de tu proyecto. Claude Code analizará la estructura, detectará el sistema de build, los frameworks y los patrones, y generará un CLAUDE.md base que puedes refinar.
## CLAUDE.md vs Auto Memory
Claude Code tiene dos mecanismos de persistencia que se complementan:
| | CLAUDE.md | Auto Memory |
|---|-----------|-------------|
| **Quién lo escribe** | Tú | Claude |
| **Contiene** | Instrucciones y reglas | Aprendizajes y preferencias |
| **Alcance** | Proyecto, usuario u organización | Por directorio de trabajo |
| **Uso** | Convenciones, arquitectura, workflows | Comandos descubiertos, patrones observados |
No compiten: CLAUDE.md define las reglas del juego, Auto Memory recuerda las jugadas.
## Errores comunes
**Claude no sigue mis instrucciones:**
- Verifica que el fichero se carga ejecutando `/memory`.
- Sé más específico: las instrucciones vagas se ignoran.
- Busca contradicciones entre ficheros si tienes varios.
- Poda si supera las 200 líneas.
**El fichero es demasiado largo:**
- Mueve el detalle a ficheros separados con `@path`.
- Usa `.claude/rules/` para organizar por tema.
- Elimina todo lo que Claude puede deducir leyendo el código.
**Las instrucciones desaparecen tras `/compact`:**
- CLAUDE.md sobrevive a la compactación, eso no es el problema.
- Si una instrucción desapareció, solo estaba en la conversación. Añádela al fichero.
## Mantenimiento
Trata CLAUDE.md como código:
1. Revísalo periódicamente, especialmente tras cambios grandes en el proyecto.
2. Elimina lo que ya no aplique.
3. Añade los patrones nuevos que surjan.
4. Haz PR de los cambios para que el equipo los revise.
Un CLAUDE.md desactualizado es peor que no tener ninguno, porque Claude tomará decisiones basándose en información incorrecta.
## Conclusión
CLAUDE.md es el fichero más rentable que puedes añadir a tu proyecto si trabajas con Claude Code. Unas pocas líneas bien escritas ahorran horas de repetición y mejoran drásticamente la calidad de las respuestas. Empieza con `/init`, refina con lo que sabes de tu proyecto, y mantelo vivo.
---
# Optimización de Nginx para WordPress: FastCGI Cache y Rate Limiting
URL: https://javiervalencia.net/post/optimizacion-de-nginx-para-wordpress-fastcgi-cache-y-rate-limiting
Si tienes un WordPress corriendo sobre **Nginx + PHP-FPM**, estas dos configuraciones van a mejorar drásticamente el rendimiento y la seguridad de tu sitio: **FastCGI Cache** para servir páginas a velocidad de vértigo y **Rate Limiting** para proteger las rutas más atacadas.
Todo esto asume que ya tienes Nginx sirviendo WordPress a través de PHP 8.4 mediante un socket Unix (`php8.4-fpm.sock`).
## FastCGI Cache: sirve WordPress sin tocar PHP

La idea es simple: cuando un visitante anónimo solicita una página, Nginx la procesa una vez a través de PHP-FPM y guarda el resultado en disco. Las siguientes visitas a esa misma URL se sirven directamente desde la caché de Nginx, sin que PHP ni la base de datos se enteren.
### Definir la zona de caché
En el bloque `http {}` de tu `nginx.conf`:
```nginx
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2
keys_zone=WORDPRESS:100m
inactive=60m
max_size=512m
use_temp_path=off;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
```
- **`keys_zone=WORDPRESS:100m`**: reserva 100 MB en memoria para las claves de caché. Suficiente para millones de entradas.
- **`inactive=60m`**: si una entrada no se accede en 60 minutos, se elimina.
- **`max_size=512m`**: tamaño máximo en disco. Ajústalo según tu espacio disponible.
- **`use_temp_path=off`**: escribe directamente en el directorio de caché, evitando una copia intermedia.
- **`fastcgi_ignore_headers`**: PHP y WordPress envían headers que invalidarían la caché innecesariamente. Los ignoramos.
### Decidir qué NO cachear
No todo debe cachearse. Los usuarios logueados, el panel de administración, las búsquedas y las páginas de WooCommerce (carrito, checkout, mi cuenta) deben llegar siempre a PHP:
```nginx
set $skip_cache 0;
# Peticiones POST
if ($request_method = POST) { set $skip_cache 1; }
# URLs con query strings
if ($query_string != "") { set $skip_cache 1; }
# Rutas de administración y dinámicas
if ($request_uri ~* "/wp-admin/|/wp-login.php|/xmlrpc.php|/wp-cron.php|/cart/|/checkout/|/my-account/") {
set $skip_cache 1;
}
# Cookies de usuario logueado o carrito
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|woocommerce_cart_hash|woocommerce_items_in_cart") {
set $skip_cache 1;
}
```
### Aplicar la caché en el bloque PHP
```nginx
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Caché
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 301 302 60m;
fastcgi_cache_valid 404 1m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
# Resiliencia
fastcgi_cache_use_stale error timeout updating invalid_header http_500 http_503;
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 5s;
fastcgi_cache_background_update on;
# Header de debug
add_header X-FastCGI-Cache $upstream_cache_status;
}
```
Tres directivas clave aquí:
- **`fastcgi_cache_use_stale ... updating`**: si PHP-FPM falla o la caché está refrescándose, Nginx sirve la versión anterior en lugar de devolver un error. Tu sitio se vuelve prácticamente indestructible ante picos de tráfico.
- **`fastcgi_cache_lock on`**: ante un cache MISS con múltiples peticiones simultáneas a la misma URL, solo una pasa a PHP-FPM. El resto espera el resultado. Evita la «estampida» de requests idénticos.
- **`fastcgi_cache_background_update on`**: cuando una entrada está a punto de expirar, Nginx la refresca en segundo plano mientras sigue sirviendo la versión actual.
### Archivos estáticos
Los archivos estáticos no deben pasar por PHP. Sírvelos directamente con cabeceras de caché agresivas:
```nginx
location ~* \.(js|css|png|jpg|jpeg|gif|webp|avif|ico|svg|woff2?|ttf|eot|otf)$ {
expires 365d;
access_log off;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
```
### Buffers y compresión
```nginx
# Buffers FastCGI
fastcgi_buffer_size 32k;
fastcgi_buffers 16 16k;
fastcgi_busy_buffers_size 32k;
# Gzip
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript
image/svg+xml application/x-font-ttf font/opentype;
```
### Crear el directorio y verificar
```bash
mkdir -p /var/cache/nginx/fastcgi
chown www-data:www-data /var/cache/nginx/fastcgi
nginx -t && systemctl reload nginx
```
Para comprobar que funciona:
```bash
curl -I https://tudominio.com | grep X-FastCGI-Cache
```
La primera visita mostrará `MISS`, la segunda `HIT`. Si ves `BYPASS`, es que la petición cumple alguna de las reglas de exclusión (usuario logueado, POST, etc.).
## Rate Limiting: proteger las rutas sensibles
WordPress tiene varias rutas que son objetivo constante de ataques de fuerza bruta y abuso. Vamos a limitar cuántas peticiones por segundo puede hacer una misma IP a cada una.
### Definir las zonas
En el bloque `http {}`:
```nginx
limit_req_zone $binary_remote_addr zone=login:10m rate=3r/m;
limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=1r/m;
limit_req_zone $binary_remote_addr zone=admin:10m rate=10r/s;
limit_req_log_level warn;
```
Cada zona usa `$binary_remote_addr` (la IP del cliente en formato binario, ocupa solo 16 bytes para IPv6) como clave y reserva 10 MB de memoria compartida.
### Aplicar los límites
```nginx
# wp-login.php — 3 peticiones por minuto
location = /wp-login.php {
limit_req zone=login burst=5 nodelay;
limit_req_status 429;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# xmlrpc.php — 1 petición por minuto (o bloquearlo directamente)
location = /xmlrpc.php {
# Opción A: rate limit
limit_req zone=xmlrpc burst=2 nodelay;
limit_req_status 429;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Opción B: bloquearlo del todo
# deny all;
# return 444;
}
# wp-admin — más permisivo para uso normal
location /wp-admin/ {
limit_req zone=admin burst=20 nodelay;
limit_req_status 429;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
# wp-cron.php
location = /wp-cron.php {
limit_req zone=xmlrpc burst=2 nodelay;
limit_req_status 429;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
```
### Tabla resumen de límites
| Ruta | Rate | Burst | Motivo |
|---|---|---|---|
| `wp-login.php` | 3/min | 5 | Protección contra fuerza bruta |
| `xmlrpc.php` | 1/min | 2 | Vector de ataque habitual |
| `wp-admin/` | 10/s | 20 | Permite uso normal, frena abuso |
| `wp-cron.php` | 1/min | 2 | Evitar abuso de cron externo |
El parámetro **`burst`** define cuántas peticiones extra se permiten por encima del rate antes de empezar a rechazar. Con **`nodelay`**, esas peticiones extra del burst se procesan inmediatamente en lugar de encolarse.
Cuando una IP supera el límite, recibe un **HTTP 429 Too Many Requests**.
### Monitorizar los bloqueos
```bash
tail -f /var/log/nginx/error.log | grep "limiting"
```
### Nota sobre xmlrpc.php
Si no usas aplicaciones externas que se conecten a tu WordPress mediante XML-RPC (la app móvil de WordPress, Jetpack, pingbacks…), bloquéalo directamente con `return 444`. Es el vector de ataque más explotado en WordPress, usado tanto para fuerza bruta como para ataques de amplificación DDoS.
## Resultado

Con estas dos configuraciones:
- **Los visitantes anónimos** reciben las páginas directamente desde la caché de Nginx, sin que PHP ni MySQL intervengan. El tiempo de respuesta pasa de cientos de milisegundos a unos pocos.
- **Los picos de tráfico** se absorben sin problemas gracias a `fastcgi_cache_lock` y `fastcgi_cache_use_stale`.
- **Los ataques de fuerza bruta** contra wp-login.php y xmlrpc.php quedan limitados a unas pocas peticiones por minuto por IP.
- **PHP-FPM** se dedica solo a lo que realmente necesita procesamiento dinámico: el panel de administración, usuarios logueados y formularios.
---
# Rails desde cero (III): ActiveRecord avanzado
URL: https://javiervalencia.net/post/rails-desde-cero-iii-activerecord-avanzado
*Tercera entrega de la serie **[Rails desde cero](/search?tag=rails-desde-cero)**. Tiempo de lectura estimado: 15 minutos.*
Si ya tienes claro cómo funcionan los modelos, las validaciones y las asociaciones básicas, es hora de hablar de lo que realmente separa una aplicación Rails mantenible de una que se convierte en un problema. En este artículo cubrimos los temas que más impactan en el rendimiento, la legibilidad y la escalabilidad: el problema N+1, eager loading, consultas avanzadas, el uso correcto de callbacks, y cómo estructurar la lógica de negocio cuando los modelos empiezan a crecer.
## El problema N+1: el error más común en Rails

El problema N+1 es probablemente el bug de rendimiento más frecuente en aplicaciones Rails, y lo más traicionero es que no se ve en el código: se ve en los logs.
Imagina este controlador y esta vista:
```ruby
# app/controllers/articles_controller.rb
def index
@articles = Article.published.recent
end
```
```ruby
<% @articles.each do |article| %>
<%= article.title %> — por <%= article.user.name %>
<% end %>
```
A simple vista parece correcto. Pero en los logs verás algo así:
```ruby
Article Load (1.2ms) SELECT "articles".* FROM "articles" WHERE ...
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3
...
```
Una consulta para cargar los artículos, y luego **una consulta por cada artículo** para cargar su usuario. Si tienes 100 artículos, son 101 consultas. Si tienes 1.000, son 1.001. Esto es el problema N+1.
### La solución: `includes`
```ruby
@articles = Article.published.recent.includes(:user)
```
Con `includes`, ActiveRecord carga todos los usuarios necesarios en **una sola consulta adicional**:
```ruby
Article Load (1.2ms) SELECT "articles".* FROM "articles" WHERE ...
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2, 3, ...)
```
De N+1 consultas a 2. La diferencia en rendimiento puede ser de órdenes de magnitud.
### `includes` vs `eager_load` vs `preload`
Rails tiene tres métodos para cargar asociaciones de forma anticipada, y no son intercambiables:
**`preload`** siempre usa dos consultas separadas, independientemente de si necesitas filtrar por la asociación:
```ruby
Article.preload(:user)
# SELECT * FROM articles
# SELECT * FROM users WHERE id IN (...)
```
**`eager_load`** hace un `LEFT OUTER JOIN` en una sola consulta. Es necesario cuando quieres filtrar u ordenar por campos de la asociación:
```ruby
Article.eager_load(:user).where(users: { role: "admin" })
# SELECT articles.*, users.* FROM articles
# LEFT OUTER JOIN users ON users.id = articles.user_id
# WHERE users.role = 'admin'
```
**`includes`** decide automáticamente entre los dos anteriores según el contexto. En la mayoría de casos es la opción correcta.
```ruby
# Usa preload (dos consultas)
Article.includes(:user)
# Detecta que necesita JOIN y usa eager_load automáticamente
Article.includes(:user).where(users: { role: "admin" })
```
### Detectar N+1 en desarrollo
La gema [Bullet](https://github.com/flyerhzm/bullet) detecta automáticamente problemas N+1 y te avisa en el log o con alertas en el navegador. Es casi obligatoria en cualquier proyecto Rails serio:
```ruby
# Gemfile
gem "bullet", group: :development
# config/environments/development.rb
config.after_initialize do
Bullet.enable = true
Bullet.rails_logger = true
Bullet.add_footer = true
end
```
## Consultas avanzadas
### Joins
Cuando necesitas consultar datos de tablas relacionadas sin cargar los objetos asociados en memoria:
```ruby
# Artículos que tienen al menos un comentario
Article.joins(:comments).distinct
# Artículos de usuarios con rol admin
Article.joins(:user).where(users: { role: "admin" })
# Varios joins encadenados
Article.joins(:user, :tags).where(tags: { name: "rails" })
```
La diferencia clave entre `joins` e `includes`: `joins` hace el JOIN para filtrar, pero **no carga los objetos asociados en memoria**. Si luego accedes a `article.user` dentro de un bucle, vuelves al problema N+1. `includes` sí carga los objetos.
En la práctica: usa `joins` para filtrar, usa `includes` para mostrar datos asociados, y combínalos cuando necesitas ambas cosas:
```ruby
Article.joins(:user)
.includes(:tags)
.where(users: { role: "admin" })
.order("users.name")
```
### Seleccionar columnas específicas
Cuando manejas tablas con muchas columnas o campos grandes, cargar solo lo que necesitas mejora el rendimiento:
```ruby
Article.select(:id, :title, :published_at)
```
Cuidado: los atributos que no hayas seleccionado lanzarán un `ActiveModel::MissingAttributeError` si intentas acceder a ellos.
### SQL arbitrario con `Arel` y literales
Para consultas que la API de ActiveRecord no puede expresar directamente:
```ruby
# Literal SQL donde sea necesario
Article.where("published_at > NOW() - INTERVAL '7 days'")
# Con Arel para algo más portable
Article.where(Article.arel_table[:published_at].gt(7.days.ago))
# Subconsultas
popular_tag_ids = Tag.where("articles_count > ?", 100).select(:id)
Article.joins(:tags).where(tags: { id: popular_tag_ids })
```
### Agrupación y agregados
```ruby
# Artículos publicados por mes
Article.published
.group("DATE_TRUNC('month', published_at)")
.count
# Media de comentarios por artículo
Article.joins(:comments)
.group(:id)
.average("comments.count")
# Los 5 autores con más artículos publicados
User.joins(:articles)
.where(articles: { published: true })
.group(:id)
.order("COUNT(articles.id) DESC")
.limit(5)
.select("users.*, COUNT(articles.id) AS articles_count")
```
### `find_each` y `find_in_batches` para grandes volúmenes
`Article.all` carga todos los registros en memoria de una vez. Con tablas grandes, esto puede consumir gigabytes de RAM. La solución es procesar en lotes:
```ruby
# Procesa los registros de 1000 en 1000 (por defecto)
Article.find_each do |article|
article.regenerate_slug!
end
# Con tamaño de lote personalizado
Article.published.find_each(batch_size: 500) do |article|
ArticleIndexJob.perform_later(article.id)
end
# Si necesitas el lote completo como array
Article.find_in_batches(batch_size: 200) do |articles|
ElasticsearchClient.bulk_index(articles)
end
```
## Callbacks: cuándo usarlos y cuándo no

Los callbacks son tentadores porque hacen que el modelo sea «inteligente». Pero un modelo con demasiados callbacks se convierte en una caja negra: es difícil saber qué va a pasar cuando llamas a `save`, difícil de testear y difícil de razonar.
### Cuándo los callbacks son apropiados
Los callbacks son adecuados para lógica que siempre debe ocurrir junto con el cambio de estado del registro, sin excepciones:
```ruby
class Article < ApplicationRecord
before_save :generate_slug
before_save :set_word_count
private
def generate_slug
self.slug = title.parameterize if title_changed?
end
def set_word_count
self.word_count = body.to_s.split.length
end
end
```
Calcular un slug o un contador de palabras a partir de atributos del propio modelo es un uso perfecto: es pura lógica interna que siempre debe mantenerse consistente.
### Cuándo los callbacks son un problema
El problema aparece cuando los callbacks tienen efectos secundarios en el exterior: enviar emails, encolar jobs, modificar otros modelos, hacer llamadas HTTP…
```ruby
# ❌ Problemático
class Article < ApplicationRecord
after_create :send_notification_email
after_create :notify_external_api
after_destroy :update_author_stats
private
def send_notification_email
ArticleMailer.published(self).deliver_later
end
end
```
El problema es que ahora no puedes crear un artículo en tests sin que se intente enviar un email. No puedes crear artículos de prueba en seeds sin efectos secundarios. Y si el job falla, ¿sabes que fue porque se lanzó desde un callback?
La alternativa es ser explícito en el lugar donde ocurre la acción de negocio:
```ruby
# ✅ Explícito en el servicio o controlador
class ArticlesController < ApplicationController
def create
@article = Article.new(article_params)
if @article.save
ArticleMailer.published(@article).deliver_later
ArticlePublishedJob.perform_later(@article.id)
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
end
```
### `after_commit` vs `after_save`
Si realmente necesitas un callback con efectos secundarios, usa `after_commit` en lugar de `after_save`. La razón es que `after_save` se ejecuta dentro de la transacción, y si algo falla después, la transacción se revierte pero el efecto secundario (el email, el job) ya se habrá lanzado.
```ruby
# ❌ Se ejecuta dentro de la transacción
after_save :enqueue_index_job
# ✅ Se ejecuta solo cuando la transacción se confirma
after_commit :enqueue_index_job, on: :create
```
## Mantener los modelos limpios
A medida que una aplicación crece, los modelos tienden a acumular lógica de negocio hasta convertirse en «fat models»: cientos de líneas, decenas de métodos, callbacks entrelazados. La comunidad Rails ha desarrollado varios patrones para combatir esto.
### Concerns
Los concerns son módulos que encapsulan un conjunto de comportamientos reutilizables. Rails los carga automáticamente desde `app/models/concerns/`:
```ruby
# app/models/concerns/publishable.rb
module Publishable
extend ActiveSupport::Concern
included do
scope :published, -> { where(published: true) }
scope :unpublished, -> { where(published: false) }
validates :published_at, presence: true, if: :published?
end
def publish!
update!(published: true, published_at: Time.current)
end
def unpublish!
update!(published: false, published_at: nil)
end
def published?
published? && published_at.present?
end
end
# app/models/article.rb
class Article < ApplicationRecord
include Publishable
end
```
Los concerns son útiles para comportamientos genuinamente reutilizables entre modelos (como `Publishable`, `Searchable` o `Auditable`). No son una solución para simplemente mover código a otro archivo: si el concern solo lo usa un modelo, probablemente ese código debería estar en el modelo directamente.
### Objetos de servicio
Para lógica de negocio compleja que involucra múltiples modelos o efectos secundarios, los objetos de servicio son una opción muy popular:
```ruby
# app/services/article_publisher.rb
class ArticlePublisher
def initialize(article, publisher:)
@article = article
@publisher = publisher
end
def call
return false unless @publisher.can_publish?(@article)
ActiveRecord::Base.transaction do
@article.publish!
@publisher.increment!(:published_articles_count)
AuditLog.create!(action: "article_published", actor: @publisher, target: @article)
end
ArticleMailer.published(@article).deliver_later
true
rescue ActiveRecord::RecordInvalid => e
@article.errors.add(:base, e.message)
false
end
end
# En el controlador
def publish
result = ArticlePublisher.new(@article, publisher: current_user).call
if result
redirect_to @article, notice: "Artículo publicado."
else
render :edit, status: :unprocessable_entity
end
end
```
El servicio encapsula toda la operación: la validación de permisos, la transacción, el log de auditoría y el envío del email. El modelo y el controlador quedan limpios.
### Query Objects
Para consultas complejas que no encajan bien en un scope, los Query Objects son una alternativa limpia:
```ruby
# app/queries/popular_articles_query.rb
class PopularArticlesQuery
def initialize(relation = Article.all)
@relation = relation
end
def call(limit: 10, since: 1.month.ago)
@relation
.published
.joins(:comments)
.where("articles.published_at > ?", since)
.group("articles.id")
.order("COUNT(comments.id) DESC")
.limit(limit)
.select("articles.*, COUNT(comments.id) AS comments_count")
end
end
# Uso
PopularArticlesQuery.new.call(limit: 5)
PopularArticlesQuery.new(Article.by_author(current_user)).call
```
## Transacciones

Cuando necesitas que varias operaciones en base de datos ocurran juntas o no ocurran en absoluto, usa una transacción explícita:
```ruby
ActiveRecord::Base.transaction do
order = Order.create!(user: current_user, total: cart.total)
cart.items.each do |item|
order.line_items.create!(product: item.product, quantity: item.quantity)
item.product.decrement!(:stock, item.quantity)
end
cart.destroy!
end
```
Si cualquier operación dentro del bloque lanza una excepción, toda la transacción se revierte. Importante: solo las excepciones hacen rollback, los `false` no. Por eso dentro de transacciones deberías usar siempre los métodos con `!`.
Si necesitas revertir manualmente desde dentro de la transacción:
```ruby
ActiveRecord::Base.transaction do
do_something
raise ActiveRecord::Rollback if some_condition
do_something_else
end
```
`ActiveRecord::Rollback` es especial: hace rollback pero no propaga la excepción fuera del bloque.
## Locking: concurrencia y condiciones de carrera
Cuando múltiples procesos pueden modificar el mismo registro simultáneamente, necesitas alguna estrategia de locking.
### Optimistic locking
Ideal cuando los conflictos son poco frecuentes. Rails lo soporta de forma nativa con una columna `lock_version`:
```ruby
# En la migración
add_column :articles, :lock_version, :integer, default: 0
# ActiveRecord lo gestiona solo:
article_a = Article.find(1)
article_b = Article.find(1) # misma versión
article_a.update!(title: "Versión A") # ok, lock_version pasa a 1
article_b.update!(title: "Versión B") # lanza ActiveRecord::StaleObjectError
```
### Pessimistic locking
Para operaciones críticas donde no puedes permitir conflictos:
```ruby
Article.transaction do
article = Article.lock.find(1) # SELECT ... FOR UPDATE
article.increment!(:views_count)
end
```
`lock` bloquea la fila a nivel de base de datos hasta que la transacción termina. Úsalo con cuidado y en transacciones cortas para evitar deadlocks.
## Algunos patrones útiles
Para cerrar el artículo, algunos métodos y patrones que aparecen constantemente en código Rails maduro:
```ruby
# find_or_create_by: busca o crea atómicamente
tag = Tag.find_or_create_by(name: "ruby")
# upsert y upsert_all: insert or update eficiente sin pasar por Ruby
Article.upsert({ id: 1, title: "Actualizado", updated_at: Time.current })
# upsert_all para operaciones masivas
Article.upsert_all(articles_data, unique_by: :slug)
# update_all: actualiza sin cargar objetos en memoria
Article.where(status: "pending").update_all(status: "draft", updated_at: Time.current)
# delete_all: elimina sin cargar ni ejecutar callbacks
Article.where("created_at < ?", 1.year.ago).delete_all
# touch: actualiza solo el timestamp
article.touch # updated_at = now
article.touch(:published_at) # campo específico
# Dirty tracking: saber qué cambió antes de guardar
article.title = "Nuevo título"
article.title_changed? # => true
article.title_was # => "Título anterior"
article.changes # => { "title" => ["Título anterior", "Nuevo título"] }
```
## Resumen
ActiveRecord es una herramienta extremadamente poderosa, pero como cualquier herramienta poderosa, hay que saber dónde aprieta:
- **N+1**: el problema más común y el más fácil de evitar con `includes`. Instala Bullet y no lo ignores.
- **Joins vs includes**: distintos propósitos, a menudo complementarios.
- **Callbacks**: úsalos para lógica interna del modelo, no para efectos secundarios.
- **`after_commit`** en lugar de `after_save` cuando los efectos secundarios son inevitables.
- **Modelos limpios**: concerns para comportamientos reutilizables, servicios para orquestar operaciones complejas, Query Objects para consultas sofisticadas.
- **Transacciones y locking** cuando la consistencia de datos no es negociable.
Con estos patrones, la diferencia entre una aplicación que aguanta el crecimiento y una que se vuelve un problema empieza a ser visible muy pronto.
En el próximo artículo hablaremos de **testing en Rails**: cómo testear modelos, controladores y servicios, y cómo estructurar una suite de tests que sea útil y no un lastre.
*¿Has visto alguno de estos patrones en tu código? ¿Tienes dudas sobre cuándo aplicar cada uno? Los comentarios son tuyos.*
---
# Rails desde cero (II): Introducción a ActiveRecord
URL: https://javiervalencia.net/post/rails-desde-cero-ii-introduccion-a-activerecord
*Segunda entrega de la serie **[Rails desde cero](/search?tag=rails-desde-cero)**. Tiempo de lectura estimado: 10 minutos.*
ActiveRecord es el corazón de cualquier aplicación Rails. Es el ORM (*Object-Relational Mapper*) que traduce las filas de tu base de datos en objetos Ruby, y tus objetos Ruby en SQL. Gracias a él puedes trabajar con los datos de forma expresiva y natural sin escribir casi una sola línea de SQL.
En este artículo veremos los fundamentos: cómo se definen los modelos, cómo funcionan las asociaciones, cómo se validan los datos y cómo se hacen consultas. En el siguiente artículo profundizaremos en los aspectos avanzados que marcan la diferencia en proyectos reales.
## El modelo y la tabla

La convención básica de ActiveRecord es que cada modelo se corresponde con una tabla en la base de datos. El nombre del modelo es singular y en CamelCase; el de la tabla es plural y en snake_case. Rails hace la conversión automáticamente:
| Modelo | Tabla |
|---|---|
| `Article` | `articles` |
| `User` | `users` |
| `BlogPost` | `blog_posts` |
| `OrderItem` | `order_items` |
Todo modelo de Rails hereda de `ApplicationRecord`:
```ruby
class Article < ApplicationRecord
end
```
Con solo eso, Rails ya sabe cómo mapear el modelo a la tabla `articles`, qué columnas tiene (las lee del esquema) y cómo hacer operaciones CRUD sobre él. No hace falta declarar atributos ni tipos: ActiveRecord los infiere directamente de la base de datos.
## CRUD: las operaciones básicas
ActiveRecord te da métodos para las cuatro operaciones fundamentales sobre cualquier registro.
### Crear
```ruby
# new + save (dos pasos)
article = Article.new(title: "Mi primer artículo", body: "Hola mundo")
article.save # => true si pasa las validaciones, false si no
# create (un solo paso, devuelve el objeto)
article = Article.create(title: "Mi primer artículo", body: "Hola mundo")
# create! (lanza excepción si falla)
article = Article.create!(title: "Mi primer artículo", body: "Hola mundo")
```
La diferencia entre `save` y `save!` (o entre `create` y `create!`) es importante: los métodos con `!` lanzan una excepción `ActiveRecord::RecordInvalid` si el registro no pasa las validaciones, mientras que los que no la llevan devuelven `false`. En aplicaciones reales, `create!` y `save!` son más seguros porque te obligan a manejar el error explícitamente.
### Leer
```ruby
# Por id (lanza ActiveRecord::RecordNotFound si no existe)
Article.find(1)
# Por condiciones (devuelve nil si no existe)
Article.find_by(title: "Mi primer artículo")
# Todos los registros
Article.all
# Con condiciones
Article.where(published: true)
# El primero y el último
Article.first
Article.last
```
### Actualizar
```ruby
article = Article.find(1)
# update_attributes
article.update(title: "Título actualizado")
# Asignación directa + save
article.title = "Título actualizado"
article.save
```
### Eliminar
```ruby
article = Article.find(1)
article.destroy # ejecuta callbacks y validaciones
article.delete # elimina directamente sin callbacks
```
La distinción entre `destroy` y `delete` es relevante: `destroy` ejecuta todos los callbacks definidos en el modelo (lo veremos más adelante), mientras que `delete` va directo a la base de datos. En general, usa `destroy` salvo que tengas una razón de rendimiento para lo contrario.
## Validaciones

Las validaciones son reglas que un registro debe cumplir para poder guardarse en la base de datos. Se definen en el modelo y se ejecutan automáticamente antes de cualquier `save` o `create`.
```ruby
class Article < ApplicationRecord
validates :title, presence: true, length: { minimum: 5, maximum: 100 }
validates :body, presence: true
validates :slug, uniqueness: true, format: { with: /\A[a-z0-9-]+\z/ }
validates :status, inclusion: { in: %w[draft published archived] }
end
```
Los validadores más habituales son:
- `presence`: el campo no puede estar vacío
- `uniqueness`: el valor debe ser único en la tabla
- `length`: controla la longitud de strings
- `numericality`: verifica que el valor sea numérico
- `format`: valida contra una expresión regular
- `inclusion` / `exclusion`: el valor debe (o no debe) estar en una lista
Cuando un registro no pasa las validaciones, Rails lo indica en el objeto `errors`:
```ruby
article = Article.new(title: "Hi")
article.valid? # => false
article.errors.full_messages
# => ["Title is too short (minimum is 5 characters)", "Body can't be blank"]
article.errors[:title]
# => ["is too short (minimum is 5 characters)"]
```
Puedes también escribir validaciones personalizadas:
```ruby
class Article < ApplicationRecord
validate :title_cannot_contain_forbidden_words
private
def title_cannot_contain_forbidden_words
forbidden = %w[spam clickbait]
if forbidden.any? { |word| title.to_s.downcase.include?(word) }
errors.add(:title, "contiene palabras no permitidas")
end
end
end
```
## Asociaciones
Las asociaciones definen las relaciones entre modelos. ActiveRecord soporta los tipos más comunes con una sintaxis muy declarativa.
### `belongs_to` y `has_many`
La asociación más frecuente: un artículo pertenece a un autor, y un autor tiene muchos artículos.
```ruby
class User < ApplicationRecord
has_many :articles, dependent: :destroy
end
class Article < ApplicationRecord
belongs_to :user
end
```
La tabla `articles` tendrá una columna `user_id` que es la clave foránea. Rails lo infiere del nombre de la asociación.
Con esto disponemos de métodos muy expresivos:
```ruby
user = User.find(1)
user.articles # todos los artículos del usuario
user.articles.count # cuántos tiene
user.articles.create!(title: "Nuevo", body: "Contenido") # crear asociado
article = Article.find(1)
article.user # el usuario al que pertenece
article.user.name # acceder a atributos del usuario
```
### `has_one`
Cuando la relación es de uno a uno:
```ruby
class User < ApplicationRecord
has_one :profile
end
class Profile < ApplicationRecord
belongs_to :user
end
```
### `has_many :through`
Para relaciones muchos a muchos con una tabla intermedia:
```ruby
class Article < ApplicationRecord
has_many :article_tags
has_many :tags, through: :article_tags
end
class Tag < ApplicationRecord
has_many :article_tags
has_many :articles, through: :article_tags
end
class ArticleTag < ApplicationRecord
belongs_to :article
belongs_to :tag
end
```
```ruby
article.tags # todos los tags del artículo
tag.articles # todos los artículos con ese tag
article.tags << Tag.find_by(name: "rails") # añadir un tag
```
### `has_and_belongs_to_many`
Una alternativa más sencilla para muchos a muchos sin necesidad de modelo intermedio, cuando la tabla de unión no necesita atributos propios:
```ruby
class Article < ApplicationRecord
has_and_belongs_to_many :tags
end
```
En la práctica, `has_many :through` es preferible porque te da más control y flexibilidad si la relación evoluciona.
## Consultas básicas

ActiveRecord tiene una API de consultas muy expresiva que genera SQL de forma automática.
```ruby
# Condiciones simples
Article.where(published: true)
Article.where(status: ["draft", "published"])
# Condiciones con string (cuidado con la inyección SQL)
Article.where("created_at > ?", 1.week.ago)
Article.where("title LIKE ?", "%rails%")
# Ordenar
Article.order(created_at: :desc)
Article.order(:title)
# Limitar resultados
Article.limit(10)
Article.offset(20).limit(10) # paginación manual
# Seleccionar columnas específicas
Article.select(:id, :title, :published_at)
# Contar, sumar, promediar
Article.count
Article.where(published: true).count
Article.average(:reading_time)
```
Una de las grandes ventajas de ActiveRecord es que las consultas son **lazy**: no se ejecutan hasta que realmente necesitas los datos. Esto permite encadenar condiciones de forma natural:
```ruby
query = Article.where(published: true)
query = query.where("created_at > ?", 1.month.ago) if filter_recent
query = query.order(:title) if sort_by_title
query.limit(10) # aquí se ejecuta el SQL
```
## Scopes: consultas reutilizables
Los scopes te permiten dar nombre a consultas frecuentes y reutilizarlas de forma encadenable:
```ruby
class Article < ApplicationRecord
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
scope :by_author, ->(user) { where(user: user) }
end
```
```ruby
Article.published # artículos publicados
Article.published.recent # publicados, más recientes primero
Article.published.recent.limit(5) # los 5 más recientes publicados
Article.by_author(current_user).recent # del usuario actual, ordenados
```
Los scopes son equivalentes a métodos de clase, pero tienen la ventaja de ser siempre encadenables y de devolver siempre un `ActiveRecord::Relation` (nunca `nil`), lo que los hace más seguros en cadenas de consultas.
## Callbacks
Los callbacks son métodos que se ejecutan automáticamente en momentos concretos del ciclo de vida de un registro: antes de guardarse, después de crearse, antes de eliminarse, etc.
```ruby
class Article < ApplicationRecord
before_save :generate_slug
after_create :notify_subscribers
before_destroy :archive_comments
private
def generate_slug
self.slug = title.parameterize
end
def notify_subscribers
NotificationJob.perform_later(id)
end
end
```
Los hooks disponibles son:
- `before_validation` / `after_validation`
- `before_save` / `after_save`
- `before_create` / `after_create`
- `before_update` / `after_update`
- `before_destroy` / `after_destroy`
- `after_commit` / `after_rollback`
Los callbacks son útiles, pero hay que usarlos con cabeza. Un modelo lleno de callbacks que disparan jobs, envían emails y modifican otros modelos se vuelve muy difícil de testear y razonar. Eso lo veremos en detalle en el artículo avanzado.
## Migraciones y el esquema
Ya vimos las migraciones en el artículo de fundamentos, pero vale la pena recordar la relación directa con los modelos: **el esquema manda**. ActiveRecord lee las columnas de la base de datos en tiempo de ejecución, así que si añades una columna en una migración, el modelo la tendrá disponible automáticamente sin tocar el código Ruby.
```ruby
# Añadir una columna a articles
bin/rails generate migration AddPublishedAtToArticles published_at:datetime
# La migración generada:
class AddPublishedAtToArticles < ActiveRecord::Migration[7.1]
def change
add_column :articles, :published_at, :datetime
end
end
bin/rails db:migrate
```
A partir de ahí, `article.published_at` ya funciona sin más cambios.
## Resumen
Con lo que hemos visto en este artículo ya tienes las herramientas para hacer prácticamente cualquier cosa básica con ActiveRecord:
- Crear, leer, actualizar y eliminar registros con una API expresiva en Ruby.
- Definir validaciones que protegen la integridad de tus datos.
- Modelar relaciones entre entidades con asociaciones.
- Escribir consultas legibles y encadenables.
- Reutilizar lógica de consulta con scopes.
- Reaccionar a eventos del ciclo de vida con callbacks.
En el siguiente artículo subimos el nivel: consultas N+1 y cómo evitarlas, eager loading, consultas complejas, el uso correcto de callbacks, y patrones para mantener los modelos limpios cuando la lógica de negocio crece.
*¿Algo que no haya quedado claro? Déjalo en los comentarios.*
---
# Rails desde cero (I): Los fundamentos que todo desarrollador debería conocer
URL: https://javiervalencia.net/post/rails-desde-cero-i-los-fundamentos-que-todo-desarrollador-deberia-conocer
*Primera entrega de la serie **[Rails desde cero](/search?tag=rails-desde-cero)**. Tiempo de lectura estimado: 10 minutos.*
Ruby on Rails cumplió veinte años en 2024 y sigue siendo uno de los frameworks más productivos que existen. Startups como GitHub, Shopify o Basecamp se construyeron sobre él, y todavía hoy impulsan negocios con millones de usuarios. Si estás empezando con Rails, o quieres consolidar tus bases antes de avanzar hacia temas más complejos, este artículo es para ti.
Vamos a ver qué es Rails, cómo piensa, y cómo están estructurados sus componentes fundamentales.
## Qué es Rails y por qué importa

Rails es un framework web escrito en Ruby que sigue la filosofía de que **las decisiones ya tomadas no deberían volverte a costar tiempo**. En lugar de configurar desde cero dónde van los modelos, cómo se conectan con la base de datos o cómo se nombran las rutas, Rails lo decide por ti mediante convenciones.
Esto se resume en dos principios que deberías grabarte a fuego:
**Convention over Configuration (CoC).** Si sigues las convenciones del framework, apenas necesitas configuración. Un modelo llamado `Article` se mapeará automáticamente a una tabla `articles`, y Rails sabrá dónde encontrar sus vistas, sus controladores y sus tests sin que le digas nada más.
**Don’t Repeat Yourself (DRY).** Cada pieza de conocimiento debe tener una única representación en el sistema. Rails te da las herramientas para evitar duplicar lógica, y te penaliza (suavemente) cuando ignoras este principio.
Estos dos principios no son decorativos. Son los que hacen que una aplicación Rails sea predecible: cualquier desarrollador que conozca el framework puede navegar un proyecto que no ha visto nunca y orientarse en minutos.
## La arquitectura MVC
Rails implementa el patrón **Model-View-Controller (MVC)**, que divide la aplicación en tres capas con responsabilidades bien diferenciadas.
### El Modelo
El modelo representa los datos y las reglas de negocio. En Rails, los modelos son clases Ruby que heredan de `ApplicationRecord` (que a su vez hereda de `ActiveRecord::Base`), y cada uno se corresponde con una tabla en la base de datos.
```ruby
class Article < ApplicationRecord
validates :title, presence: true, length: { minimum: 5 }
validates :body, presence: true
belongs_to :author, class_name: "User"
has_many :comments, dependent: :destroy
end
```
ActiveRecord es el ORM de Rails. Traduce objetos Ruby a filas de base de datos y viceversa, y te da una API en Ruby para hacer consultas sin escribir SQL directamente:
```ruby
# Todos los artículos publicados, ordenados por fecha
Article.where(published: true).order(created_at: :desc)
# El artículo con id 42
Article.find(42)
# Crear y persistir un artículo en una línea
Article.create!(title: "Hola Rails", body: "Primer artículo", published: true)
```
### La Vista
Las vistas son las plantillas que generan la respuesta que el usuario recibe. En Rails se usan por defecto archivos `.html.erb` (Embedded Ruby), aunque puedes usar otros motores como Haml o Slim.
```ruby
Artículos
<% @articles.each do |article| %>
<%= link_to article.title, article_path(article) %>
<%= article.body.truncate(200) %>
Por <%= article.author.name %>
<% end %>
```
La diferencia entre `<% %>` y `<%= %>` es importante: el primero ejecuta código Ruby sin imprimir nada; el segundo ejecuta e imprime el resultado en el HTML.
Las vistas no deberían contener lógica compleja. Si una vista empieza a llenarse de condicionales y bucles enrevesados, es una señal de que esa lógica pertenece al modelo o a un helper.
### El Controlador
El controlador es el intermediario. Recibe la petición HTTP, consulta los modelos que necesita, y escoge qué vista renderizar.
```ruby
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
def index
@articles = Article.where(published: true).order(created_at: :desc)
end
def show
# @article ya está disponible gracias al before_action
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article, notice: "Artículo creado correctamente."
else
render :new, status: :unprocessable_entity
end
end
private
def set_article
@article = Article.find(params[:id])
end
def article_params
params.require(:article).permit(:title, :body, :published)
end
end
```
Fíjate en `article_params`: esto es **Strong Parameters**, el mecanismo de Rails para evitar que un usuario malintencionado envíe campos que no debería poder modificar. Siempre debes usar `permit` para declarar explícitamente qué atributos acepta el controlador.
## El router: el mapa de tu aplicación

El router de Rails (`config/routes.rb`) define qué URL corresponde a qué acción de qué controlador. La forma más común de declarar rutas es con `resources`:
```ruby
Rails.application.routes.draw do
resources :articles do
resources :comments, only: [:create, :destroy]
end
root "articles#index"
end
```
Una sola línea `resources :articles` genera automáticamente siete rutas RESTful:
| Verbo HTTP | URL | Acción | Propósito |
|---|---|---|---|
| GET | /articles | index | Listar artículos |
| GET | /articles/new | new | Formulario de creación |
| POST | /articles | create | Crear artículo |
| GET | /articles/:id | show | Ver un artículo |
| GET | /articles/:id/edit | edit | Formulario de edición |
| PATCH/PUT | /articles/:id | update | Actualizar artículo |
| DELETE | /articles/:id | destroy | Eliminar artículo |
Puedes ver todas las rutas de tu aplicación en cualquier momento con:
```bash
bin/rails routes
```
## Migraciones: el control de versiones de tu base de datos
Las migraciones son archivos Ruby que describen cambios en el esquema de la base de datos. En lugar de ejecutar SQL directamente, escribes migraciones que Rails puede aplicar, revertir y compartir con el resto del equipo.
```ruby
# db/migrate/20240315120000_create_articles.rb
class CreateArticles < ActiveRecord::Migration[7.1]
def change
create_table :articles do |t|
t.string :title, null: false
t.text :body, null: false
t.boolean :published, default: false, null: false
t.references :author, null: false, foreign_key: { to_table: :users }
t.timestamps
end
add_index :articles, :published
end
end
```
`t.timestamps` añade automáticamente las columnas `created_at` y `updated_at`, que Rails gestiona solo. `t.references` crea la columna `author_id` y, con `foreign_key: true`, la restricción en base de datos.
Para aplicar la migración:
```bash
bin/rails db:migrate
```
Para revertirla:
```bash
bin/rails db:rollback
```
El estado actual del esquema siempre queda reflejado en `db/schema.rb`, que es la fuente de verdad sobre cómo está la base de datos.
## La consola de Rails: tu mejor aliada

Una de las herramientas más útiles de Rails es su consola interactiva, que carga toda la aplicación y te permite ejecutar código Ruby contra ella en tiempo real:
```bash
bin/rails console
```
Desde ahí puedes hacer consultas, probar validaciones, crear registros de prueba o depurar lógica de negocio sin necesidad de pasar por el navegador:
```ruby
# En la consola
article = Article.new(title: "Test")
article.valid? # => false
article.errors.full_messages # => ["Body can't be blank"]
Article.count # => 42
Article.last.title # => "Rails desde cero (I): Los fundamentos..."
```
En desarrollo es habitual usar `bin/rails console --sandbox`, que ejecuta todo en una transacción que se revierte al salir, sin tocar los datos reales.
## Estructura de directorios
Una aplicación Rails tiene una estructura de carpetas estándar que merece la pena conocer desde el principio:
```bash
app/
models/ # Lógica de negocio y datos
controllers/ # Controladores HTTP
views/ # Plantillas HTML
helpers/ # Helpers para las vistas
jobs/ # Trabajos en segundo plano
mailers/ # Envío de emails
channels/ # ActionCable (WebSockets)
config/
routes.rb # Definición de rutas
database.yml # Configuración de base de datos
application.rb # Configuración de la aplicación
db/
migrate/ # Migraciones
schema.rb # Estado actual del esquema
seeds.rb # Datos iniciales
test/ (o spec/) # Tests
Gemfile # Dependencias Ruby
```
Todo tiene su lugar. Cuando dudes dónde poner algo, la respuesta suele estar en esta estructura.
## Generadores: andamiaje automático
Rails incluye generadores que crean los archivos necesarios por ti, siguiendo todas las convenciones:
```bash
# Genera modelo + migración
bin/rails generate model Article title:string body:text published:boolean
# Genera controlador con acciones
bin/rails generate controller Articles index show new create edit update destroy
# Genera modelo + migración + controlador + vistas + rutas (scaffold completo)
bin/rails generate scaffold Article title:string body:text published:boolean
```
El scaffold es útil para aprender o para prototipar rápido, pero en proyectos reales rara vez querrás usarlo tal cual, porque genera código genérico que casi siempre necesitarás personalizar.
## Por dónde seguir
En este artículo hemos visto los cimientos: MVC, ActiveRecord, el router, las migraciones y la estructura de la aplicación. Con estos conceptos ya puedes leer código Rails real y entender lo que hace.
En los próximos artículos de la serie profundizaremos en:
- **ActiveRecord avanzado**: asociaciones, scopes, callbacks y consultas complejas.
- **Testing en Rails**: RSpec, fixtures, factories y la pirámide de tests.
- **Background jobs**: Solid Queue, Active Job y cuándo procesar en segundo plano.
- **Autenticación**: desde cero con `has_secure_password` y con Authentication Generator.
- **APIs con Rails**: modo API, serialización y versionado.
Si tienes preguntas o quieres que profundice en algún aspecto concreto, déjalo en los comentarios.
*¿Te ha resultado útil? Comparte el artículo con alguien que esté aprendiendo Rails.*
---
# MySQL y MariaDB, MariaDB y MySQL
URL: https://javiervalencia.net/post/mysql-y-mariadb-mariadb-y-mysql
## ¿MySQL o MariaDB? Una guía práctica con 10 criterios para elegir

Elegir entre MySQL y MariaDB no es solo cuestión de nombres parecidos. Aunque comparten historia y sintaxis, hoy son productos con caminos distintos. Vamos a diseccionar los factores más relevantes para tomar una decisión bien informada.
### 1) **Propiedad del proyecto y licencia**
La base de todo es quién controla el desarrollo y bajo qué términos se distribuye.
MySQL es desarrollado y controlado por Oracle, con un modelo de **doble licencia** (GPL + opción empresarial propietaria). Esto puede significar que algunas características queden solo para clientes de pago.
MariaDB, por su parte, está bajo la **licencia GPL completa** y es impulsado por la MariaDB Foundation, con desarrollo abierto y mayor transparencia de roadmap para la comunidad.
**Cuándo importa:** si necesitas total libertad de uso y modificación, MariaDB puede ser más atractivo; si necesitas soporte empresarial sólido de un proveedor grande, MySQL tiene ventajas claras.
### 2) **Compatibilidad binaria y migración**
Al principio MariaDB fue un *drop‑in replacement* de MySQL: prácticamente podías reemplazar uno por otro sin tocar tu aplicación.
Con el tiempo esa compatibilidad ha disminuido: desde MySQL 8 en adelante, hay funciones y estructuras que no son compatibles automáticamente con MariaDB.
**Cuándo importa:** si ya tienes una base de datos existente, evalúa qué tan profundas son las dependencias en funciones específicas de MySQL antes de migrar.
### 3) **Motores de almacenamiento y flexibilidad**
MariaDB ofrece una gama más amplia de motores de almacenamiento (como *Aria*, *ColumnStore*, *Spider* o *MyRocks*) para adaptarse a distintos casos de uso.
MySQL, en cambio, se centra principalmente en *InnoDB* (muy sólido para la mayoría de casos) y ofrece menos motores adicionales en su edición comunitaria.
**Ventaja:** MariaDB para cargas mixtas o especializadas; MySQL si InnoDB resuelve todas tus necesidades y quieres simplicidad.
### 4) **Rendimiento y escalabilidad**
En varios benchmarks y comparativas, MariaDB suele mostrar mejoras de rendimiento en consultas complejas, replicación o concurrencia de conexiones altas.
MySQL ofrece rendimiento constante y ha incorporado optimizaciones importantes en sus versiones recientes, aunque a veces con enfoque empresarial.
**Cuándo importa:** en proyectos de alto rendimiento o arquitecturas distribuidas, vale la pena probar con tus propias cargas de trabajo.
### 5) **Alta disponibilidad y clustering**
Ambos soportan replicación y clustering, pero MariaDB incluye desde versiones abiertas soluciones avanzadas de replicación multi‑master y failover automático como parte de su ecosistema.
MySQL también ofrece clustering y replicación, pero algunas características clave a menudo se reservan para su edición empresarial.
**Relevancia:** sistemas que no pueden permitirse downtime o que requieren escalabilidad horizontal pueden preferir MariaDB por sus opciones integradas.
### 6) **Soporte de JSON y tipos de datos modernos**
MySQL tiene soporte fuerte y nativo para JSON (almacenado como objeto binario) con funciones especializadas.
MariaDB también soporta JSON, pero lo maneja como alias de *LONGTEXT* y difiere en algunas funciones específicas.
**Impacto:** si tu aplicación depende intensamente de manipulación de JSON dentro de la base de datos, compara cuidadosamente las funciones disponibles.
### 7) **Seguridad y autenticación**
Ambos ofrecen mecanismos de autenticación segura, cifrado en reposo y en tránsito, así como gestión de roles y permisos.
MariaDB incluso amplía el conjunto de plugins de validación de contraseña y ofrece cifrado a nivel de tablas temporales y binlogs.
**Decisión:** revisa los requisitos de tu normativa o compliance; la diferencia puede ser sutil, pero puede inclinar tu elección.
### 8) **Ecosistema de herramientas y soporte**
MySQL tiene una enorme base de usuarios y amplio soporte en herramientas de terceros, desde IDEs hasta soluciones de monitoreo y administración.
MariaDB mantiene compatibilidad con la mayoría de estas herramientas y aporta sus propias, como *MariaDB Shell* y *MaxScale* para enrutamiento y balanceo.
**Consejo:** si dependes de una herramienta específica en tu stack, verifica primero la compatibilidad con ambas.
### 9) **Comunidad vs. control corporativo**
MariaDB mantiene su desarrollo más dirigido por la comunidad abierta, lo que tiende a resultar en nuevas funciones y parches más rápidos.
MySQL, con control centralizado de Oracle, prioriza estabilidad y conservadurismo en nuevas funciones, lo que para algunos entornos empresariales es una virtud.
**Reflexión:** si valoras innovación rápida y colaboración abierta, MariaDB tiene ventaja; si prefieres estabilidad con Roadmap corporativo, MySQL puede gustarte más.
### 10) **Soporte empresarial y servicios gestionados**
MySQL cuenta con el respaldo de Oracle, lo que incluye contratos de soporte firmados por una gran empresa.
MariaDB también ofrece soporte empresarial a través de MariaDB Corporation, pero la estructura y costos pueden ser distintos.
Además, ambos están disponibles como servicios gestionados en plataformas populares (AWS, GCP, Azure), aunque las configuraciones y limitaciones pueden variar según cada proveedor.
**Relevancia:** si tu proyecto necesita SLA rígidos o acompañamiento profesional, compara también los acuerdos de soporte y el coste total de propiedad.
## Conclusión práctica
No hay una respuesta universal: la elección depende de las **prioridades de tu proyecto**. Si lo que buscas es **máxima libertad de código abierto, flexibilidad y características avanzadas**, MariaDB suele ser la opción más atractiva. Si en cambio priorizas **estabilidad, amplio soporte de herramientas y respaldo corporativo fuerte**, MySQL puede encajar mejor.
| Criterio | MySQL | MariaDB | Comentario |
|---|---|---|---|
| **Propiedad y licencia** | Oracle, GPL/Comercial | MariaDB Foundation, GPL puro | MariaDB más libre para proyectos abiertos |
| **Compatibilidad y migración** | Compatible con versiones anteriores de MySQL, pero divergente desde MySQL 8 | Compatible con MySQL 5.5/5.7, drop‑in inicial | Migrar de MySQL antiguo a MariaDB suele ser sencillo |
| **Motores de almacenamiento** | InnoDB, limitado | InnoDB, Aria, ColumnStore, MyRocks, Spider | MariaDB ofrece más flexibilidad según caso de uso |
| **Rendimiento y escalabilidad** | Estable y optimizado, especialmente en entornos Oracle | Mejores benchmarks en consultas complejas y replicación | Depende de carga y tipo de consultas |
| **Alta disponibilidad / clustering** | MySQL Cluster (edición empresarial), replicación estándar | Multi‑master, replicación avanzada, MaxScale integrado | MariaDB aporta HA más accesible en open source |
| **Soporte JSON / tipos modernos** | JSON nativo, funciones avanzadas | JSON como alias de LONGTEXT, funciones limitadas | MySQL mejor si se trabaja intensamente con JSON |
| **Seguridad / autenticación** | Roles, cifrado, plugins Oracle | Roles, cifrado, plugins adicionales, validación de passwords | MariaDB añade opciones extras open source |
| **Ecosistema de herramientas** | Amplia compatibilidad, IDEs y monitoreo | Compatible con la mayoría, plus MaxScale y MariaDB Shell | Ambas tienen buena cobertura, MySQL más maduro |
| **Comunidad vs. control corporativo** | Estabilidad y roadmap controlado por Oracle | Comunidad activa, desarrollo rápido y abierto | MariaDB más innovador, MySQL más conservador |
| **Soporte empresarial / servicios gestionados** | SLA Oracle, amplio soporte comercial | SLA MariaDB Corporation, opciones flexibles | Ambos disponibles en servicios cloud gestionados |
Esta tabla resume lo que ya discutimos: MariaDB es más **flexible, abierto y con funciones avanzadas**, mientras que MySQL es más **estable, ampliamente soportado y respaldado por Oracle**.
---
# Fizzy
URL: https://javiervalencia.net/post/fizzy
### Kanban sin complicaciones para equipos y proyectos ligeros

Un tablero kanban bien diseñado puede hacer que organizar ideas, errores y pequeñas tareas vaya tanto más rápido como el café de la mañana. **Fizzy** es justamente eso: una aplicación de seguimiento de tareas y gestión estilo kanban enfocada en la **simplicidad absoluta**, sin volverse un monstruo de opciones imposibles de dominar.
Fizzy nació en Basecamp/37signals como una respuesta al crecimiento indiscriminado de herramientas tradicionales: Trello que se hincha, Jira que parece un ERP, Asana tratando de ser *todo para todos*. Fizzy elige otra senda: **tarjetas, columnas, colores y nada más superfluo**.
#### ¿Qué es exactamente Fizzy?
Fizzy es una **herramienta de tableros estilo kanban** donde puedes:
- Crear tarjetas para ideas, tareas, bugs o cualquier cosa que quieras seguir.
- Organizar esas tarjetas en columnas personalizables.
- Moverlas, cerrar las completadas y mantener un flujo de trabajo visual y claro.
- Ver notificaciones, usar etiquetas y exponer tableros públicos si lo deseas.
No se vende como *gestión de proyectos completa*, sino como **un lanzador de trabajo ligero**. Es ideal para equipos pequeños, proyectos independientes o simplemente cuando no necesitas todo un tablero lleno de métricas y configuraciones complejas.
Además, el proyecto es **open source**: puedes ejecutarlo tú mismo, modificarlo o adaptarlo a tus necesidades sin pagar nada.
### Imágenes mentales: lo que *verdaderamente* hace Fizzy
Piensa en Fizzy como un tablero de post-its digital que ya viene organizado para ti. No hay sprints, no hay backlog oculto, no hay módulos con 15 páginas de documentación. Solo **columnas y tarjetas**, con unas pocas capacidades extras que realmente *importan*: notificaciones que aparecen cuando las tarjetas llevan tiempo ignoradas, webhooks para integrarse con Slack/Campfire, y gestión visual sencilla. ([serverspace.io](https://serverspace.io/es/support/help/fizzy-how-to-set-up-and-use-the-kanban-tracker-from-37signals-a-complete-guide/?utm_source=chatgpt.com))
### Instalar Fizzy con Docker: rápido, directo y sin alharacas
Una de las mejores sorpresas fue lo **sencillo** que resulta levantar Fizzy con Docker. El repositorio oficial ya incluye un **Dockerfile** que te permite construir la imagen y ponerla en marcha casi sin pensar.
La mayoría de los proyectos Rails open source que he probado tienen *mil dependencias*, configuraciones complejas y pasos que nunca funcionan a la primera. Con Fizzy no es así: bajas el código, construyes la imagen y en cuestión de minutos tienes el sistema funcionando y listo para recibir tráfico si quieres.
No hay que luchar con montones de variables crípticas para hacer que arranque, ni buscar extensos guiones de despliegue. Ese enfoque de **menos es más** se nota también en cómo está pensado para contenedores. Para alguien que está acostumbrado a peregrinar entre manuales y errores crípticos, esto ya es un alivio.
### Uso diario: sin distracciones
Cuando entras a Fizzy, no te recibe una pared de botones. Ves tus tableros, tus columnas y tus tarjetas. Siendo un defensor de las herramientas que *desaparecen cuando no las necesitas*, Fizzy logra justamente eso: **no compites con la herramienta, compites con tu trabajo**.
Mover una tarjeta de “Pendiente” a “Haciendo” es inmediato. Crear columnas, etiquetar cosas, cerrar elementos completados… todo se hace con un clic o arrastrando. Persiste en esa filosofía de “hacer lo esencial bien” que rara vez se encuentra fuera de aplicaciones minimalistas clásicas.
Fizzy no pretende ser un gestor de proyectos completo ni una suite repleta de módulos. Es, en cambio, un **tablero kanban elegante y directo** que hace lo que promete sin darte dolores de cabeza ni necesidad de horas de configuración. Que funcione tan bien con Docker desde el primer intento es una señal clara de la intención de los creadores: **menos complejidad, más productividad real**.
Si valoras una herramienta que se adapte a ti y no al revés, Fizzy merece un lugar en tu flujo de trabajo.
Si quieres puedo añadir capturas de ejemplo o un paso a paso detallado de un *docker compose* básico para Fizzy.
---
# Traducción y resumen de la guía Shape Up de Basecamp (Stop Running in Circles and Ship Work That Matters)
URL: https://javiervalencia.net/post/traduccion-y-resumen-de-la-guia-shape-up-de-basecamp-stop-running-in-circles-and-ship-work-that-matters
## **Shape Up – Metodología de desarrollo de producto (Basecamp)**

### **Qué es Shape Up**
Shape Up es una metodología para desarrollar productos digitales creada en Basecamp que propone ciclos de trabajo de seis semanas con un enfoque en *definir bien el trabajo antes de empezarlo*, dar autonomía a los equipos y reducir el riesgo de no entregar a tiempo. ([uiFromMars](https://www.uifrommars.com/que-es-shape-up/?utm_source=chatgpt.com))
En esencia, Shape Up intenta evitar los problemas comunes de otras metodologías (backlogs interminables, reuniones excesivas, microgestión) proponiendo ciclos más largos, mayor responsabilidad de los equipos y trabajos mejor preparados antes de asignarlos. ([Blog de TI](https://blog.geekhunter.com.br/shape-up-ti-metodologia/?utm_source=chatgpt.com))
## **1. Shaping — Dar forma al trabajo antes de construirlo**
La fase de *shaping* es diseño conceptual y preparación. Aquí se define qué se hará, cómo se piensa resolver y qué límites tiene el proyecto antes de que el equipo lo construya. ([Basecamp](https://basecamp.com/shapeup/1.1-chapter-02?utm_source=chatgpt.com))
**Principios clave de *shaping*:**
- **Nivel adecuado de detalle:**
No demasiado concreto (como un diseño final) que bloquee la creatividad, ni demasiado abstracto que no dé orientación. Debe ser lo justo para que el equipo sepa qué hacer. ([Ben Travis](https://benjamintravis.com/blog/shape-up?utm_source=chatgpt.com))
- **Establecer límites y “apetito”:**
Antes que estimar cuánto tardará algo, se decide *cuánto tiempo se quiere dedicar* (p. ej., seis semanas). Esto ayuda a limitar la solución al valor que merece. ([Ben Travis](https://benjamintravis.com/blog/shape-up?utm_source=chatgpt.com))
- **Riesgos y huecos (“rabbit holes”):**
Identificar partes del trabajo que pueden salirse de control o ser inciertas, y eliminarlos o acotarlos antes de pasar al equipo. ([Sebastien Phlix](https://www.sebastienphlix.com/book-summaries/singer-shape-up?utm_source=chatgpt.com))
- **Pitch:**
El resultado del *shaping* es un “pitch” o propuesta con 5 ingredientes:
problema, apetito, solución, riesgos y exclusiones (qué no se hace). ([Sebastien Phlix](https://www.sebastienphlix.com/book-summaries/singer-shape-up?utm_source=chatgpt.com))
## **2. Betting — La mesa de apuestas**

Luego de que varias propuestas están *shaped*, un grupo de decisión (a veces llamado “mesa de apuestas”) elige cuáles proyectos se van a trabajar en el próximo ciclo de seis semanas. ([ProductPlan](https://www.productplan.com/glossary/shape-up-method/?utm_source=chatgpt.com))
**Ideas clave:**
- **No hay backlog tradicional:**
En lugar de una lista infinita de tareas, solo hay un conjunto pequeño de propuestas para cada ciclo. ([prodify.group](https://www.prodify.group/blog/book-report-5-key-takeaways-from-shape-up-by-basecamps-ryan-singer?utm_source=chatgpt.com))
- **Ciclos de 6 semanas + *cooldown*:**
Las apuestas se planifican para seis semanas sin extender plazos. Si al final del ciclo no está completo, lo que no esté hecho se descarta o replantea para otro ciclo. ([gemba.es](https://www.gemba.es/p/olvidate-de-scrum-llega-shape-up?utm_source=chatgpt.com))
- **Período de *cooldown* de ~2 semanas:**
Es un espacio para arreglar bugs, hacer mantenimiento, refactorizaciones o preparar la siguiente ronda de *shaping*. ([Engenharia de Software Moderna](https://engsoftmoderna.info/artigos/shape-up.html?utm_source=chatgpt.com))
Este enfoque con ciclos fijos y sin extensiones fomenta foco y hace que el equipo reaprenda a tomar decisiones sobre alcance y prioridades. ([ProductPlan](https://www.productplan.com/glossary/shape-up-method/?utm_source=chatgpt.com))
## **3. Building — Construir con autonomía**
Una vez que un proyecto está elegido para un ciclo, el equipo lo *construye* con un alto grado de responsabilidad:
- **Equipos pequeños e integrados:**
Diseñadores y desarrolladores trabajan juntos en la solución. ([ProductPlan](https://www.productplan.com/glossary/shape-up-method/?utm_source=chatgpt.com))
- **Progreso de extremo a extremo:**
En lugar de fragmentar por roles (solo backend, luego frontend), se busca completar “pedazos verticales” que entreguen funcionalidad real. ([prodify.group](https://www.prodify.group/blog/book-report-5-key-takeaways-from-shape-up-by-basecamps-ryan-singer?utm_source=chatgpt.com))
- **Organizar por scopes, no por tareas:**
El equipo mapea y divide trabajo por “ámbitos” que representan partes completas del producto, y prioriza lo desconocido primero. ([Ben Travis](https://benjamintravis.com/blog/shape-up?utm_source=chatgpt.com))
- **Mostrar progreso visual:**
Se usan técnicas como la “hill chart” (gráfico de colina) para entender qué está resuelto y qué sigue siendo incierto. ([prodify.group](https://www.prodify.group/blog/book-report-5-key-takeaways-from-shape-up-by-basecamps-ryan-singer?utm_source=chatgpt.com))
## **Principios generales del método**

- **Tiempo fijo, alcance variable:**
El tiempo para un ciclo es fijo (seis semanas), pero *lo que finalmente se construye* puede cambiar de alcance según lo aprendido. ([ProductPlan](https://www.productplan.com/glossary/shape-up-method/?utm_source=chatgpt.com))
- **Menos micromanagement:**
Los equipos deciden cómo cumplir con los objetivos dentro de los límites dados, lo que reduce reuniones innecesarias y control externo. ([Blog de TI](https://blog.geekhunter.com.br/shape-up-ti-metodologia/?utm_source=chatgpt.com))
- **Resolución temprana de riesgos:**
Al hacer *shaping* se enfrentan incertidumbres antes de que se conviertan en problemas caros o bloqueos. ([Basecamp](https://basecamp.com/shapeup/1.1-chapter-02?utm_source=chatgpt.com))
## **Resumen de flujo**
1. **Shaping:** preparar propuestas completas con límites y riesgos claros.
2. **Betting:** elegir qué se construye en el ciclo de seis semanas.
3. **Building:** el equipo construye con autonomía y foco en valor real.
4. **Cooldown:** espacio para reflexionar, arreglar y preparar el próximo ciclo. ([ProductPlan](https://www.productplan.com/glossary/shape-up-method/?utm_source=chatgpt.com))
---
# Resumen charla Dave Thomas
URL: https://javiervalencia.net/post/resumen-charla-dave-thomas
**Contexto y quién habla**
Dave Thomas es una figura legendaria en la comunidad Ruby: coautor de *The Pragmatic Programmer*, uno de los firmantes iniciales del Manifiesto Ágil, y autor de varios libros influyentes sobre Ruby y desarrollo de software. Su experiencia de décadas hace que lo que propone esté menos en plan “dogma” y más como una invitación a replantear nuestras suposiciones. ([RubyEvents.org](https://www.rubyevents.org/profiles/pragdave?utm_source=chatgpt.com))
### **La tesis central: recalibrar cómo estructuramos código en Ruby**

Thomas parte de una observación crítica: “Estamos escribiendo nuestro código Ruby de forma equivocada”. Esa frase es más que provocadora: es una invitación a desafiar un hábito que se ha vuelto casi automático. ([sfruby.com](https://sfruby.com/schedule/?utm_source=chatgpt.com))
Su propuesta principal es:
**Dejar de usar clases como la unidad fundamental de diseño en Ruby cuando no es necesario.**
Esto no significa abolir clases—sino reconsiderar su papel —especialmente cuando alternativas más simples pueden hacer el código más claro, mantenible y flexibles a cambios futuros. ([sfruby.com](https://sfruby.com/schedule/?utm_source=chatgpt.com))
### **¿Por qué parar con clases?**
Ruby es un lenguaje extremadamente expresivo y flexible: las clases son solo una de muchas herramientas para estructurar código. Dave Thomas argumenta que:
- Hemos desarrollado una “dependencia mental” en clases por tradición más que por necesidad real.
- Diseñar todo alrededor de clases a menudo nos lleva a patrones complejos (“design patterns”) que en muchos casos son artefactos culturales de lenguajes más rígidos (como Java o C++), no de Ruby. ([sfruby.com](https://sfruby.com/schedule/?utm_source=chatgpt.com))
En otras palabras: las clases **no son la unidad más natural de pensamiento en Ruby**.
### **Un enfoque alternativo: estructura desde el crecimiento real**
Cuando diseñamos software tradicionalmente:
1. Pensamos en la estructura primero.
2. Escribimos clases y jerarquías.
3. Luego codificamos.
Thomas propone algo más parecido a trabajar desde exploración y crecimiento orgánico:
1. Comienza con pequeñas funciones, módulos y bloques que reflejen exactamente lo que necesitas ahora.
2. Permite que la estructura emerja conforme el problema y su complejidad crecen.
3. Solo introduce clases o abstracciones más grandes si se vuelve claro que aportan valor. ([sfruby.com](https://sfruby.com/schedule/?utm_source=chatgpt.com))
Esto es similar al enfoque evolutivo propio del desarrollo ágil, aplicado a la forma de escribir código mismo.
### **Ventajas de este enfoque**
Dave Thomas señala beneficios prácticos:
**Simplicidad**
Menos artefactos ceremoniales (clases complejas, jerarquías rígidas) significan menos sobrecarga mental, menos convenciones que memorizar, y código que “dice lo que hace”. ([sfruby.com](https://sfruby.com/schedule/?utm_source=chatgpt.com))
**Mantenibilidad real**
Cuando la lógica está en funciones o módulos centrados en acciones claras, es más fácil modificar y razonar sobre ellos. Esto encaja con la filosofía de mantener el código flexible y adaptativo (un principio clave del desarrollo ágil). ([sfruby.com](https://sfruby.com/schedule/?utm_source=chatgpt.com))
**Menos dependencias innecesarias**
Menos clases puede significar menos dependencias entre partes del sistema, lo que hace más fácil reusar, testear y reemplazar componentes. ([sfruby.com](https://sfruby.com/schedule/?utm_source=chatgpt.com))
### **¿Qué pasa con patrones clásicos de diseño y metodologías?**
Otra parte de la charla—y probablemente la más filosófica—es que muchos de los patrones de diseño que aprendemos y aplicamos vienen del mundo de lenguajes más estáticos. En Ruby, muchas veces:
- *Objetos* no son lo que pensamos.
- *Clases* no siempre son la forma más natural de expresar una abstracción.
- *DSLs (Domain Specific Languages)*, bloques y módulos pueden hacer el trabajo de formas más expresivas. ([sfruby.com](https://sfruby.com/schedule/?utm_source=chatgpt.com))
Esto no implica abandonar conceptos, sino **reapropiarlos de forma más idiomática** para Ruby.
### **Ejemplos implícitos en la charla**
Aunque la charla en sí usa ejemplos concretos (código en vivo), el patrón general que Thomas muestra es:
- Escribir funciones pequeñas y enfocadas.
- Encapsular comportamiento relevante en módulos en vez de clases generales.
- Evitar jerarquías profundas y rígidas que enmascaran la intención real del código. ([sfruby.com](https://sfruby.com/schedule/?utm_source=chatgpt.com))
Este estilo se alinea con prácticas contemporáneas que favorecen la composición y la inmutabilidad funcional cuando tiene sentido.
### **Un empujón hacia pragmatismo**
Este mensaje no tiene truco: no dice *“deja de usar clases por decreto”*, sino:
**Piensa críticamente sobre cuando realmente aportan valor y cuando simplemente seguimos patrones por costumbre.**
Ruby es un lenguaje expresivo; abrazar ese poder puede permitir código más simple y sostenible con menos artefactos sintácticos. ([sfruby.com](https://sfruby.com/schedule/?utm_source=chatgpt.com))
---
# Comparando el estilo “clásico orientado a clases” con el estilo Ruby-idiomático, funcional y modular
URL: https://javiervalencia.net/post/comparando-el-estilo-clasico-orientado-a-clases-con-el-estilo-ruby-idiomatico-funcional-y-modular
Vamos a **aterrizar la idea de Dave Thomas con código Ruby real**, comparando el estilo “clásico orientado a clases” con el estilo **Ruby-idiomático, funcional y modular**, y viendo *por qué* el segundo suele envejecer mejor.
No es una religión. Es ingeniería pragmática.
## 1. El punto de partida clásico (el reflejo Java)

Imagina un caso típico: procesar un pedido.
### Enfoque habitual con clases
```ruby
class Order
attr_reader :items, :customer
def initialize(items:, customer:)
@items = items
@customer = customer
end
def total_price
items.sum(&:price)
end
def valid?
items.any? && customer.active?
end
end
class OrderProcessor
def initialize(order)
@order = order
end
def process
raise "Invalid order" unless @order.valid?
charge_customer
send_confirmation
end
private
def charge_customer
PaymentGateway.charge(@order.customer, @order.total_price)
end
def send_confirmation
Mailer.order_confirmation(@order)
end
end
```
Esto es correcto. También es **más estructura de la necesaria**.
Problemas sutiles:
- Las clases no modelan cosas del mundo real, sino **pasos de un flujo**
- La lógica está dispersa
- Probar `OrderProcessor` implica instanciar `Order`
- La clase existe *solo* para agrupar métodos
## 2. La propuesta de Dave Thomas: empieza por acciones
Ruby no te obliga a empezar pensando en “objetos”.
Puedes empezar pensando en **verbos**.
### Enfoque funcional y plano
```ruby
def total_price(items)
items.sum(&:price)
end
def valid_order?(items, customer)
items.any? && customer.active?
end
def process_order(items:, customer:)
raise "Invalid order" unless valid_order?(items, customer)
PaymentGateway.charge(customer, total_price(items))
Mailer.order_confirmation(items, customer)
end
```
Observa algo importante:
- No hay *estado implícito*
- Cada función hace **una cosa**
- Las dependencias son explícitas
- Es trivial testear cada función
Esto **ya es Ruby de primera clase**, no un atajo.
## 3. “Pero esto queda desordenado”: módulos al rescate

Aquí es donde mucha gente se pone nerviosa.
Dave Thomas dice: *no saltes a clases, usa módulos*.
```ruby
module Orders
module Pricing
def self.total(items)
items.sum(&:price)
end
end
module Validation
def self.valid?(items, customer)
items.any? && customer.active?
end
end
module Processing
def self.process(items:, customer:)
raise "Invalid order" unless Validation.valid?(items, customer)
PaymentGateway.charge(customer, Pricing.total(items))
Mailer.order_confirmation(items, customer)
end
end
end
```
Esto aporta:
- Namespacing claro
- Ningún estado oculto
- Ninguna jerarquía artificial
- Código legible como un **mapa mental del dominio**
## 4. ¿Cuándo sí aparece una clase?
Dave Thomas no es anti-clases.
Las clases **aparecen cuando hay identidad y estado duradero**.
Ejemplo: un `Money`, un `User`, un `Session`.
```ruby
class Money
attr_reader :amount, :currency
def initialize(amount, currency)
@amount = amount
@currency = currency
end
def +(other)
raise "Currency mismatch" unless currency == other.currency
Money.new(amount + other.amount, currency)
end
end
```
Aquí la clase tiene sentido porque:
- Tiene identidad
- Encapsula invariantes
- Protege reglas internas
Lo que Dave Thomas critica es crear clases **solo para colgar métodos**.
## 5. Un ejemplo muy Rails-real (Service Objects)

El patrón clásico Rails:
```ruby
class CreateUser
def initialize(params)
@params = params
end
def call
user = User.new(@params)
user.save!
Mailer.welcome(user)
user
end
end
```
La versión “Ruby puro”:
```ruby
module Users
def self.create(params)
user = User.create!(params)
Mailer.welcome(user)
user
end
end
```
Pregunta incómoda:
👉 ¿qué aporta realmente la clase `CreateUser`?
Respuesta honesta: **nada**, salvo ceremonia.
## 6. Beneficios reales (no filosóficos)
Después de años, este estilo suele ganar porque:
- El código crece **horizontalmente**, no en jerarquías
- Refactorizar es más fácil
- Las dependencias están a la vista
- Los tests no requieren dobles complejos
- El dominio se expresa como lenguaje, no como UML
Esto es muy Ruby y muy Pragmatic Programmer.
## 7. La idea profunda del vídeo (la que no sale en el código)
La charla no va de clases.
Va de esto:
**No diseñes por anticipación la forma final del sistema.
Deja que la estructura emerja del uso real.**
Las clases son una **decisión tardía**, no el punto de partida.