Saltar al contenido
jrio.dev

Soy Jose, desde Salamanca, España.

Por aquí voy dejando mis pequeños proyectos personales.

~/jose.json
nombre
José Martín Río
linkedin
in/josemartinrio

Proyectos

Divr En producción Abril 2026divr.es ↗ Donde llevo mi cartera: acciones, movimientos, dividendos y opciones PUT, con el IRPF ya calculado. Web y aplicación móvil. De aquí salió bme-fundamentals, el día que dejé de fiarme de los datos que usaba. GoPostgresReactExpo Ver por dentro Explorar arquitecturaDiagrama interactivo
bme-fundamentals En desarrollo Agosto 2026api-production-2a50.up.railway.app ↗ Saca los estados financieros de las empresas del BME de lo que depositan en la CNMV. Lo hice porque la fuente que usaba antes devolvía ceros donde no había dato, y me comía errores sin enterarme. GoPostgresXBRL Ver por dentro Explorar arquitecturaDiagrama interactivo
Chess Coach En producción Agosto 2026chess-coach-production-5b27.up.railway.app ↗ Pegas el PGN de una partida y te dice qué jugaste mal y qué errores repites. Lo analiza Stockfish en el servidor, así que sale del motor y no de mi opinión. TypeScriptNodeStockfish Ver por dentro Explorar arquitecturaDiagrama interactivo

Sobre mí

Programo desde hace nueve años, casi siempre en la parte de atrás: APIs, bases de datos y dejar la información limpia.

Todos los proyectos nacen igual: algo que necesitaba y no encontraba hecho, o que estaba hecho de una forma de la que no me fiaba.

No son gran cosa, pero funcionan y los uso a diario.

Contacto

Si quieres contactar conmigo, puedes hacerlo en:

En producción Abril 2026

Divr

Cartera para quien invierte en dividendo a largo plazo, en el mercado español, el de Londres y el de Estados Unidos. Posiciones, movimientos, dividendos con su retención, scrip y opciones PUT.

Por qué existe

Llevaba la cartera en una hoja de cálculo que cada año se rompía por el mismo sitio: los dividendos con retención en origen y las primas de las opciones. Lo que necesitaba no era una aplicación de bolsa, era que en la declaración el número estuviera bien sin rehacerlo a mano.

De ahí sale la regla de la que cuelga todo lo demás: cada importe se pasa a euros al cambio de su propia fecha, porque la declaración necesita el cambio del día en que el dinero se movió de verdad.

Con qué está hecho

Web

React 19ViteTanStack QueryTailwind

Móvil

React NativeExpo

Servidor

Go 1.25GinpgxPostgreSQL 16

Fuera

bme-fundamentalsYahooBCEClaude

Cómo funciona por dentro

Tres clientes, una sola API y una base. Cada capa mira hacia abajo y ninguna vuelve hacia arriba.

Cliente Web Donde meto los movimientos y miro el año fiscal React · Vercel
Cliente Aplicación móvil La misma cartera en el bolsillo. Comparte con la web los tipos, los formateadores y las ayudas de cambio Expo
Entrada Capa HTTP Rutas, códigos de estado, identificación y orígenes permitidos. Aquí no se calcula dinero Go · Gin
Servicio La única capa que calcula Todo el dinero y todo el cambio de divisa viven aquí. Es lo que evita que la web y el móvil lleguen a dos resultados distintos del mismo año fiscal
Dominio Reglas puras Coste medio, resultado de una opción. Sin base de datos y sin red, así que se prueban solas
Repositorio Sólo SQL Y siempre con el dueño delante: no existe un buscar por identificador sin comprobar de quién es
Adaptadores Lo de fuera, detrás de una interfaz Cada servicio externo se puede cambiar, o falsear en un test, sin tocar una regla de negocio
Almacén PostgreSQL Migraciones numeradas que se aplican solas al arrancar y no se editan después
Fuente propia bme-fundamentals Las cuentas de los emisores españoles y su calendario de dividendos, con caché. El otro proyecto, que nació justo de aquí
Fuentes ajenas Cotización y tipo de cambio Los datos de valoración y el cambio diario del Banco Central Europeo, capturado con la fecha de la operación

Decisiones que cambiaron algo

  • Precio medio de compra, no FIFO. Es lo que aplica el IRPF español a las acciones cotizadas, así que FIFO no es una opción que falte: es una que no debe existir. Comprar recalcula la media, vender no la toca, y recalcularla al vender es la forma clásica de equivocarse aquí.
  • El cambio se guarda como lo publica el banco central, y pasar a euros es dividir. Multiplicar da un número creíble y mal. El tipo se captura con la fecha de la operación y se congela en la fila: lo viejo no se vuelve a convertir nunca.
  • La retención depende del mercado, no de la divisa. Un banco español ingresa un dividendo estadounidense en una cuenta en euros, así que la moneda en la que llega el dinero no dice nada de lo que ya se retuvo.
  • El dividendo se guarda en bruto. El neto depende de quién cobra. Lo que llega del banco es el neto, así que el importe se reconstruye y se deja corregir a mano antes de guardarlo.
  • En las opciones, el contrato son cien acciones y ese cien aparece una sola vez en todo el servidor. Si aparece dos veces, una de las dos es un fallo.
  • Todo lo de alguien se lee y se escribe con su dueño delante. En una cartera, un fallo de aislamiento no es un fallo de privacidad: es la declaración de otro.
  • Cada integración es opcional y lo dice. Sin sus claves, la sección se declara sin configurar en vez de enseñar una página vacía, que es el mismo engaño que originó bme-fundamentals con otra ropa.

Verlo funcionando

El repositorio es privado. La web está abierta en divr.es y la aplicación de Android sale del mismo sitio.

bme-fundamentals Abrir la API
En desarrollo Agosto 2026

bme-fundamentals

Los estados financieros de los emisores del BME, sacados de los registros oficiales en XBRL de la CNMV y no de un agregador.

La regla que lo gobierna todo

Una cifra no puede existir sin decir de dónde sale. O apunta al hecho del que salió, o declara la fórmula que la produjo, o dice por qué está vacía. No hay un cuarto caso, y un hueco nunca es un cero.

Por qué existe

La fuente que usaba mi cartera servía beneficio bruto y resultado de explotación como cero cuando en realidad no había dato. El problema nunca fue no tener datos: fue que los datos malos parecían buenos. Un ratio calculado sobre un cero inventado sale con la misma pinta que uno bueno.

Lo que se gana yendo al origen son diez ejercicios completos en vez de los cuatro del agregador, con beneficio por acción, flujo de explotación, inversión y dividendos pagados etiquetados de forma nativa por el propio emisor.

Con qué está hecho

Ingesta

GoXBRL 2.1Lector de PDF propio

Almacén

PostgreSQLMigraciones

Contraste

ESEFxBRL-JSONLEI

Servicio

API RESTRailwayDocker distroless

Cómo funciona por dentro

Dos capas, y la de abajo es la que manda: lo que dice el documento no se toca nunca, y todo lo demás se recalcula desde ahí.

Origen La CNMV No hay una dirección por fichero: hay que conducir su formulario con cinco peticiones por emisor para que suelte un archivo con todo su histórico
Descarga El documento, tal cual llegó Se guarda comprimido con el resumen de su contenido. Ese resumen es la idempotencia: lo ya visto no se vuelve a escribir. Va despacio a propósito, sin concurrencia Go
Capa de abajo Hechos Lo que dice el XBRL sin traducir, inmutable. Aquí no se corrige nada nunca
Capa de arriba Vista anual derivada La fila de cada ejercicio, calculada desde los hechos con los mapas de conceptos. Hay tres taxonomías (general, banca y seguros) que reutilizan los mismos códigos con significados distintos, así que el mapa va a estar mal varias veces y corregirlo tiene que ser recalcular en segundos
Control Contraste contra ESEF El informe europeo del mismo emisor, unido por su identificador de entidad. Es el único control independiente del mapa de conceptos, y de ahí no sale ni un valor servido
Fuente aparte Dividendos con fecha Las cuentas anuales dicen cuánto se pagó, no cuándo. El cuándo son actos societarios que el emisor comunica en PDF, y eso se recorre solo cada día
Salida API REST Cuentas anuales, calendario de dividendos y previsiones, cada cifra con su procedencia o con el motivo de estar vacía. Es lo que consume Divr

Decisiones que cambiaron algo

  • Dos capas separadas. Los hechos son inmutables y pesan; la vista se recalcula. Corregir un mapeo es volver a derivar en segundos y no volver a descargar ochenta megas por emisor, y deja la parte frágil como una función pura que se prueba sin red.
  • La base de datos rechaza los datos sin procedencia. No es una convención que se respeta por buena voluntad: hay restricciones que impiden escribir un cero sin origen, un valor sin documento y un hueco sin motivo.
  • El contraste existe porque un mapa mal hecho no da un error, da una cifra plausible. El resultado de banca apuntó una temporada al que incluye minoritarios en vez de al atribuido a la dominante, y sobrevivió a la investigación, al código y a un test en verde. En el informe europeo son dos conceptos distintos y se canta el primer día.
  • Una discrepancia se registra, no se resuelve. Se marca el campo y se sigue sirviendo el dato oficial. La marca dice algo sobre nuestro mapa, no sobre el emisor.
  • Una estimación no es un evento, y eso lo impone el esquema. Un evento no puede existir sin su anuncio, y la tabla de previsiones no tiene columna donde guardar uno. Confundir una fecha proyectada con una anunciada no es un descuido posible: es algo que no se puede escribir.
  • Un calendario vacío y un recolector caído son la misma respuesta. Por eso la respuesta lleva hasta cuándo se ha recorrido el registro y cuántas comunicaciones se leyeron: con doce leídas y ningún dividendo, el silencio es del emisor; con cero, el silencio es nuestro.
  • Las unidades no se infieren jamás y el orden de las fechas lo vigila la base. Una escala mal leída multiplica por mil sin romper un test, y dos fechas del ciclo de pago intercambiadas al leer un PDF es el fallo más fácil de cometer y el más difícil de ver.

Dónde está

El recorrido está completo de punta a punta: descarga, lectura, derivación, persistencia, contraste y API. Lo que falta no es código, es la primera carga de verdad y que alguien abra el informe publicado de un emisor de cada taxonomía y reconozca las cifras. Un mapa de conceptos mal escrito no produce un error, produce un dato creíble, y eso no lo cierra ningún test.

Verlo funcionando

El repositorio es privado. El servicio está levantado y su portada trae un probador: pegas una clave, eliges una ruta y la petición sale desde tu propio navegador. Las claves se emiten a mano, porque redistribuir estos registros a terceros es una pregunta que todavía no está resuelta y un alta automática la estaría respondiendo por la vía de los hechos.

Chess Coach Abrir la app
En producción Agosto 2026

Chess Coach

Pegas el PGN de una partida y te dice qué jugaste mal, qué era mejor, en qué fase pierdes más y qué errores repites de una partida a otra.

Por qué existe

Los análisis de las webs de ajedrez te dicen que una jugada fue mala, pero no que llevas veinte partidas haciendo la misma cosa. Eso es lo que quería ver, así que el histórico entero se guarda y los fallos se agrupan por tipo.

Lo que se afirma tiene que estar comprobado, en los dos sentidos. Un punto débil no se declara con tres partidas de muestra: por debajo del mínimo el hallazgo ni se calcula. Y lo que haces bien sólo se reconoce con mérito medible, como encontrar la jugada del motor justo después de un fallo del rival.

Con qué está hecho

Servidor

TypeScriptNode 20SQLite

Motor

StockfishUCILibro ECO

Clientes

ReactViteCapacitor

Entrega

RailwayDockerAPK en CI

Cómo funciona por dentro

De la partida pegada al informe. Cada caja es un paso real del recorrido.

Entrada Un PGN pegado O un enlace compartido desde el móvil, que se resuelve contra el servidor
Entrada Tus partidas de Chess.com Hasta cincuenta, buscando en los últimos seis meses de archivos
API Servidor Node Sesión con testigo aleatorio, contraseñas con scrypt y el PGN convertido en las posiciones de la partida TypeScript
Motor Varios Stockfish en paralelo Procesos aparte hablando UCI, a profundidad 14, 17 o 20 según el nivel. Cada posición de la partida se evalúa una sola vez UCI
Clasificación Cuánto costó la jugada De la pérdida en centipeones a la caída de probabilidad de victoria, y de ahí a la etiqueta
Apertura Libro ECO 3.810 entradas de lichess, para decir en qué jugada te saliste de teoría
Motivos Patrones y hábitos Pieza colgada, defensa abandonada, mate perdido, rey sin enrocar, dama temprana
Almacén SQLite en un volumen Partidas, informes completos y los agregados de etiquetas y fases. El perfil se calcula releyendo los informes, que es donde está el detalle jugada a jugada
Salida Repaso guiado y perfil Una pantalla por idea, y el perfil sobre tus últimas doscientas partidas
Opcional El botón ¿Por qué? Un modelo traduce a motivo lo que el motor ya dijo, con los hechos de la posición delante y prohibido contradecirlo Claude
Lo siguiente RAG sobre literatura Hoy el porqué razona sobre los datos del motor y no cita teoría de aperturas ni planes de un libro. Ahí es donde encaja Pendiente

La caja de trazo discontinuo no está hecha. Aparece porque es lo siguiente, no porque ya funcione.

Decisiones que cambiaron algo

  • Cada posición se evalúa una sola vez. La pérdida de una jugada es la diferencia entre la evaluación de antes y la de después, puestas las dos del lado de quien movió. Sale lo mismo que probar cada alternativa por separado y cuesta una fracción.
  • Si juegas la jugada del motor, la pérdida es cero por definición. Las dos posiciones se buscan por separado y el efecto horizonte puede hundir la evaluación aunque la jugada sea la mejor. Sin esa regla, un sacrificio correcto se castiga como un error.
  • Se clasifica por caída de probabilidad de victoria. Perder doscientos centipeones cuando vas ganando de sobra no cambia nada; perderlos en una posición igualada decide la partida. En centipeones esas dos jugadas son la misma.
  • El modelo traduce, no analiza. Sólo recibe hechos comprobables de la posición y tiene prohibido inventar variantes o llevarle la contraria al motor. Sin clave la aplicación funciona igual y esos botones ni aparecen.
  • La cuenta es tu usuario de Chess.com. Un nombre de cuenta aparte sería un dato más que mantener y que puede discrepar justo del que importa: de quién se traen las partidas y desde qué lado se leen.

Verlo funcionando

El código está en un repositorio privado, pero la aplicación está en abierto. Pide registrarse con tu usuario de Chess.com, que es lo que le dice de quién son las partidas.