¿Qué es TinerPay?
TinerPay es una plataforma fintech que permite a los usuarios crear cuentas digitales y gestionar wallets en distintas divisas. Los usuarios pueden enviar y recibir dinero en tiempo real mediante transferencias seguras registradas en una base de datos distribuida. El sistema opera entre Europa y Latinoamérica, donde la consistencia de los datos y la disponibilidad continua son críticas.
Usuario
Crea una cuenta
Wallet
Gestiona saldo
Transferencia
Envía dinero
El límite de las arquitecturas centralizadas
TinerPay opera entre Europa y Latinoamérica con millones de transacciones financieras simultáneas. Una arquitectura centralizada no puede soportar esto.
Cuello de botella: un solo servidor no escala con millones de operaciones simultáneas.
Latencia geográfica: un servidor en Europa responde lento para usuarios en Latinoamérica.
La base de datos diseñada para el entorno transaccional distribuido
Exigencias críticas
(TinerPay)
Integridad financiera
CockroachDB resuelve los tres problemas anteriores de forma nativa: replica los datos entre nodos eliminando el punto único de fallo, escala horizontalmente añadiendo nodos al cluster sin interrupciones, y distribuye los datos por regiones reduciendo la latencia para usuarios en Europa y Latinoamérica.
Por qué CockroachDB
Consistencia fuerte
Las transacciones financieras deben ejecutarse sin pérdida ni duplicación de datos.
Alta disponibilidad
Los datos se replican automáticamente entre nodos del cluster.
Escalabilidad horizontal
Permite añadir nodos al cluster para soportar más usuarios.
Distribución multi-región
Reduce la latencia para usuarios en diferentes regiones.
Análisis de requisitos
Funcionales
- Crear: usuarios, wallets, transacciones, divisas
- Consultar: saldo de una wallet, historial de transacciones, datos de usuario
- Modificar: saldo de wallet, datos de usuario
- Eliminar: transacciones, wallets, usuarios
- Transferir: dinero entre wallets de distintos usuarios
No funcionales
- Alta disponibilidad 24/7
- Consistencia fuerte (ACID)
- Persistencia de datos
- Escalabilidad horizontal
- Baja latencia global
- Tolerancia a fallos de nodos
Modelo de datos
El sistema utiliza un modelo relacional compatible con SQL. A continuación se identifican las cuatro entidades principales con sus campos, claves primarias (PK) y claves foráneas (FK).
Usuario
- id (PK · UUID)
- name
- email (UNIQUE)
- deleted_at (soft delete)
- created_at
Currency
- code (PK · STRING)
- name
- symbol
Wallet
- id (PK · UUID)
- balance
- currency_code (FK → currencies)
- user_id (FK → users)
- created_at
Transaction
- id (PK · UUID)
- from_wallet (FK → wallets)
- to_wallet (FK → wallets)
- amount
- created_at
- CHECK from ≠ to
Relaciones entre entidades
Una vez identificadas las entidades, se definen las relaciones entre ellas para construir el modelo relacional completo.
Usuario → Wallet
Un usuario puede poseer múltiples wallets dentro del sistema.
1 : NCurrency → Wallet
Una divisa puede estar asociada a múltiples wallets.
1 : NWallet → Transaction
Una wallet puede participar en múltiples transacciones como origen o destino.
1 : NEstructura de la base de datos
Inicialización y definición de tablas en CockroachDB
Crea la base de datos y la selecciona como contexto activo.
CREATE DATABASE tinerpay; USE tinerpay;
Entidad central. Añadimos deleted_at para soft delete y mantener historial completo.
CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name STRING NOT NULL, email STRING UNIQUE NOT NULL, deleted_at TIMESTAMP NULL, created_at TIMESTAMP DEFAULT now() );
Catálogo de divisas. Referenciada por wallets para garantizar integridad referencial.
CREATE TABLE currencies ( code STRING PRIMARY KEY, name STRING NOT NULL, symbol STRING NOT NULL );
Sin ON DELETE CASCADE — evitamos borrar wallets si se elimina un usuario.
CREATE TABLE wallets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id), currency_code STRING REFERENCES currencies(code), balance DECIMAL NOT NULL DEFAULT 0 CHECK (balance >= 0), created_at TIMESTAMP DEFAULT now() );
Sin CASCADE en FK — el historial financiero no se borra bajo ningún concepto.
CREATE TABLE transactions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), from_wallet UUID REFERENCES wallets(id), to_wallet UUID REFERENCES wallets(id), amount DECIMAL NOT NULL CHECK (amount > 0), created_at TIMESTAMP DEFAULT now(), CHECK (from_wallet != to_wallet) );
Índices básicos
Los índices aceleran las consultas más frecuentes del sistema.
Las claves primarias y el campo email (UNIQUE) ya tienen índice automático.
Los siguientes se crean explícitamente sobre las claves foráneas.
🔍 Wallets por usuario
CREATE INDEX ON wallets (user_id);
Permite consultar todas las wallets de un usuario sin recorrer toda la tabla.
🔍 Transacciones por wallet origen
CREATE INDEX ON transactions (from_wallet);
Acelera la consulta del historial de envíos de una wallet concreta.
🔍 Transacciones por wallet destino
CREATE INDEX ON transactions (to_wallet);
Acelera la consulta del historial de recepciones de una wallet concreta.
🔍 Wallets por divisa
CREATE INDEX ON wallets (currency_code);
Útil para filtrar wallets en una divisa específica (EUR, USD, etc.).
ℹ️ En CockroachDB los UUID como PK ya están indexados internamente. Solo se crean índices adicionales sobre los campos de búsqueda frecuente.
Operaciones SQL
Crear usuario con wallet inicial — se usa un CTE para obtener el UUID generado y asociarlo a la wallet en una sola operación atómica.
BEGIN;
WITH new_user AS (
INSERT INTO users (name, email)
VALUES ('Diego', 'diego@gmail.com')
RETURNING id
)
INSERT INTO wallets (user_id, currency_code, balance)
SELECT id, 'EUR', 0 FROM new_user;
COMMIT;
Balances de un usuario activo — usa el índice wallets(user_id).
SELECT w.balance, w.currency_code FROM wallets w JOIN users u ON w.user_id = u.id WHERE u.email = 'diego@gmail.com' AND u.deleted_at IS NULL;
Historial de envíos de una wallet — usa el índice transactions(from_wallet).
SELECT t.amount, t.created_at FROM transactions t WHERE t.from_wallet = 'uuid-wallet-origen' ORDER BY t.created_at DESC;
Historial de recepciones de una wallet — usa el índice transactions(to_wallet).
SELECT t.amount, t.created_at FROM transactions t WHERE t.to_wallet = 'uuid-wallet-destino' ORDER BY t.created_at DESC;
Wallets por divisa — usa el índice wallets(currency_code).
SELECT w.id, w.balance, u.name FROM wallets w JOIN users u ON w.user_id = u.id WHERE w.currency_code = 'EUR' AND u.deleted_at IS NULL;
JOIN con users en la propia query — solo actualiza wallets de usuarios activos.
UPDATE wallets w SET balance = balance + 100 FROM users u WHERE w.user_id = u.id AND u.email = 'diego@gmail.com' AND u.deleted_at IS NULL;
Soft delete — marcamos deleted_at en lugar de borrar, preservando el historial financiero completo.
UPDATE users SET deleted_at = now() WHERE email = 'diego@gmail.com' AND deleted_at IS NULL;
Arquitectura de TinerPay
API Backend
- 👤 Usuarios
- 💳 Wallets
- 🔄 Transacciones
Preguntas frecuentes
¿Puede un usuario tener varias wallets?
Sí. Un usuario puede poseer múltiples wallets asociadas a su cuenta.
¿Qué significa ACID?
Atomicidad, Consistencia, Aislamiento y Durabilidad.
¿Qué es CockroachDB?
Una base de datos SQL distribuida con transacciones ACID y alta disponibilidad.
¿Qué pasa si falla un nodo del cluster?
Los otros nodos siguen operando. El sistema no cae gracias al consenso Raft.
¿Por qué se usa UUID como clave primaria?
Garantiza unicidad en un sistema distribuido sin riesgo de colisión entre nodos.
¿Qué hace el CHECK en la tabla wallets?
Garantiza que el balance nunca sea negativo.
¿Por qué no puedes borrar un usuario directamente?
Por obligación legal, el historial financiero debe ser inmutable y trazable.
¿Qué es un índice y para qué sirve?
Acelera las consultas más frecuentes sin recorrer toda la tabla.
Instalación del cluster
El entorno se despliega utilizando Docker Compose.
Cómo funciona el backend y cómo se conecta con la web
Flujo completo de una acción (ejemplo: crear usuario)
El usuario pulsa un botón en la página web.
(fetch)
El backend recibe la petición, ejecuta la lógica correspondiente y realiza consultas a la base de datos.
(CockroachDB — 3 NODOS)



CockroachDB replica los datos en los 3 nodos usando el consenso Raft para mantener todo consistente y disponible.
Es el intermediario entre la web y la base de datos. Se encarga de la lógica, seguridad, validaciones y acceso controlado a los datos.
- Seguridad: la base de datos no es accesible directamente.
- Control: toda la lógica de negocio está en el backend.
- Resiliencia: si un nodo falla, el sistema sigue funcionando.
- Frontend: HTML, CSS, JavaScript
- Backend: Node.js, Express
- Base de datos: CockroachDB (distribuida, Raft)
- Comunicación: HTTP + JSON