Parte III · Diseñar bien
Capítulo 9 — Amigos y trueques (relación reflexiva)
En el capítulo anterior empezaste a guardar lo que pasa dentro del combate:
las partidas. Quién jugó contra quién, quién ganó, cuándo. Ahí ya viste un primer
detalle curioso: una partida conectaba la tabla jugador consigo misma.
Ahora vamos a por lo que pasa fuera del combate. Porque un juego de cartas no son solo duelos. Es gente que se hace amiga, que se sigue, que se pasa cartas repetidas. La parte social. Y al modelarla te vas a topar de frente con una idea que en el capítulo 8 solo asomó: una tabla que se relaciona consigo misma.
Al acabar este capítulo sabrás diseñar dos cosas nuevas: la amistad entre jugadores (un jugador es amigo de muchos otros jugadores) y el intercambio de cartas (un jugador da una carta a otro). Las dos comparten un mismo truco mental, y cuando lo veas una vez, lo reconocerás para siempre.
Las dos puntas son la misma tabla
Recordemos un momento la colección del capítulo 5. Ahí conectabas dos tablas
distintas: jugador por un lado y carta por otro. Un jugador posee muchas
cartas; una carta la poseen muchos jugadores. Muchos a muchos. Lo resolviste con
una tabla intermedia, coleccion, que tenía una clave foránea hacia cada
lado.
Ahora fíjate en la amistad. ¿Quién se hace amigo de quién? Un jugador… de otro jugador. Las dos puntas de la relación son la misma cosa: jugadores.
Eso es una relación reflexiva (también la verás llamada recursiva): una
relación en la que un tipo de entidad se conecta consigo mismo. No hay una
segunda tabla. La tabla jugador se relaciona con jugador.
Y como un jugador puede tener muchos amigos, y cada uno de esos amigos a su vez tiene muchos amigos, la amistad es de muchos a muchos. Juntando las dos ideas: la amistad es una relación N:M reflexiva. Muchos a muchos, pero con las dos patas apoyadas en la misma tabla.
En el capítulo 8 ya tuviste una primera probada de esto con
partida: era una tabla relacionada conjugadorconsigo misma, pero a través de una tabla de por medio (la propia partida, que además guardaba quién ganó, cuándo y cuánto duró). Aquí tienes el caso puro: la conexión es directa, jugador con jugador, y lo único que guardas es que esa amistad existe.
Diseñando la tabla `amistad`
El método es exactamente el mismo que usaste para la colección. Una relación N:M
se lleva a una tabla intermedia con una clave foránea por cada lado. La única
diferencia es que ahora las dos claves foráneas apuntan a la misma tabla,
jugador.
Como las dos columnas no pueden llamarse igual, les damos nombres que distingan
los dos extremos: jugador_a y jugador_b.
CREATE TABLE amistad (
jugador_a INTEGER,
jugador_b INTEGER,
fecha DATE,
PRIMARY KEY (jugador_a, jugador_b),
FOREIGN KEY (jugador_a) REFERENCES jugador(id),
FOREIGN KEY (jugador_b) REFERENCES jugador(id)
);
Léelo despacio, porque cada línea cuenta una idea que ya conoces:
jugador_ayjugador_bson los dos jugadores de la amistad. Cada uno es una clave foránea que apunta ajugador.id. Fíjate en que las dosREFERENCESvan a la misma tabla. Eso es lo único nuevo.fechaes un dato propio de la amistad: desde cuándo son amigos. Es opcional, pero queda bonito para mostrar "amigos desde marzo de 2026".- La clave primaria compuesta
(jugador_a, jugador_b)dice que una pareja de amigos no puede repetirse. La misma idea del capítulo 5, ahora aplicada a dos jugadores en vez de a un jugador y una carta.
Vamos a meter una amistad. DragoFuego99 (id 1) y LunaVerde (id 2) se hacen amigos:
INSERT INTO amistad (jugador_a, jugador_b, fecha)
VALUES (1, 2, '2026-03-18');
Y ya está. La base de datos de Kriaturas sabe que esos dos jugadores son amigos.
El plano
Aquí tienes el dibujo de lo que acabas de crear. Primero en cajas y líneas, que se lee en cualquier parte:
┌──────────────┐
│ jugador │
├──────────────┤
jugador_a (FK) │ id (PK) │
───────▶ │ alias │
jugador_b (FK) │ email │
───────▶ │ fecha_alta │
└──────────────┘
▲ ▲
│ │
┌─────┴─┴────────┐
│ amistad │
├────────────────┤
│ jugador_a (FK) │
│ jugador_b (FK) │
│ fecha │
│ PK = (a, b) │
└────────────────┘
Las dos flechas salen de amistad y entran las dos en jugador. Esa imagen —dos
líneas que vuelven a la misma caja— es la firma visual de una relación reflexiva.
Y aquí está lo mismo en formato Mermaid, que las herramientas web y GitHub dibujan
solas:
Las dos relaciones parten de jugador y llegan a amistad. Una sola tabla en el
origen, dos veces.
Un detalle precioso: la amistad va en los dos sentidos
Aquí aparece una pregunta que parece tonta pero que es puro diseño de bases de datos: si DragoFuego99 es amigo de LunaVerde, ¿LunaVerde es amiga de DragoFuego99?
Pues claro. La amistad es simétrica: si va en un sentido, va en los dos. No existe ser amigo "solo de ida".
Y ahí tienes un problemilla. Con la tabla tal cual, nada te impide guardar la misma amistad dos veces:
jugador_a | jugador_b | fecha
----------+-----------+------------
1 | 2 | 2026-03-18 ← Drago y Luna
2 | 1 | 2026-03-18 ← Luna y Drago... ¡otra vez los mismos!
Son la misma amistad escrita al revés. La clave primaria no te salva, porque
(1, 2) y (2, 1) son parejas distintas para la base de datos. Y si dejas que
pase, luego al contar amigos te saldrán el doble, y al borrar una amistad se te
quedará la otra colgando.
La solución es una regla de diseño, algo que decides tú y respetas siempre:
guarda cada amistad una sola vez, con el id más pequeño en jugador_a y el
más grande en jugador_b. Es decir, la convención jugador_a < jugador_b.
Así, la amistad de Drago (1) y Luna (2) se guarda solo como (1, 2), nunca
como (2, 1). Una pareja, una fila. Cuando luego preguntes "¿quiénes son los
amigos de Luna?", buscarás su id en las dos columnas, en jugador_a y en
jugador_b, porque puede estar en cualquiera de las dos. Pero guardado, está una
sola vez.
Esto es un ejemplo perfecto de algo que el libro repite: pensar el dato antes de guardarlo. La base de datos no sabe que la amistad es simétrica; eso es conocimiento del mundo real que tú metes en forma de regla. Una herramienta o una IA pueden generarte la tabla
amistad, pero esta decisión —cómo evitar el duplicado— es criterio tuyo. En el capítulo 11 verás incluso cómo hacer que la propia base de datos vigile reglas como esta.
Trueques: cuando el vínculo guarda más cosas
La amistad solo guardaba que existía (y desde cuándo). Pero hay vínculos entre jugadores que cargan más información. El intercambio de cartas es el ejemplo perfecto, y además es el broche de toda la parte de diseño, porque junta casi todo lo que has aprendido.
Piensa en qué pasa en un trueque. Un jugador da una carta. Otro jugador la
recibe. Se intercambia una carta concreta. Y ocurre en una fecha. Es, otra
vez, una relación entre dos jugadores (reflexiva, las dos puntas en jugador),
pero con un invitado más: la carta.
CREATE TABLE intercambio (
id INTEGER PRIMARY KEY,
jugador_da INTEGER,
jugador_recibe INTEGER,
carta_id INTEGER,
fecha DATE,
FOREIGN KEY (jugador_da) REFERENCES jugador(id),
FOREIGN KEY (jugador_recibe) REFERENCES jugador(id),
FOREIGN KEY (carta_id) REFERENCES carta(id)
);
Mira todo lo que hay aquí, porque es un repaso de medio libro en una sola tabla:
jugador_dayjugador_recibeson dos claves foráneas ajugador: la parte reflexiva. Quién entrega y quién recibe.carta_ides una clave foránea acarta: qué se intercambia.fechaconvierte cada fila en un hecho con su momento, igual que las partidas del capítulo 8. Un intercambio es un dato histórico: pasó, y lo guardas tal cual.- Y como aquí sí importa el orden (no es lo mismo dar que recibir), no hay problema de simetría: cada intercambio es una dirección concreta.
Fíjate también en por qué esta tabla sí lleva un id propio como clave
primaria, mientras que amistad no lo necesitaba. Dos jugadores son amigos una
vez y ya está: la pareja basta para identificar la fila. Pero dos jugadores pueden
intercambiar cartas muchas veces, incluso la misma carta en días distintos.
La pareja no basta. Un id propio le da a cada trueque su identidad.
Vamos a registrar un intercambio. PixelPunk (id 3) le da un Burbujo (carta id 3) a ToxiRana (id 4):
INSERT INTO intercambio (id, jugador_da, jugador_recibe, carta_id, fecha)
VALUES (1, 3, 4, 3, '2026-04-05');
Una fila, y queda registrado para siempre quién dio qué, a quién y cuándo.
El trueque "completo" tiene una trampa. En un intercambio de verdad, las dos partes dan algo a la vez: yo te doy un Burbujo y tú me das una Poción, y las dos cosas tienen que pasar juntas o no pasar ninguna. Si tu colección se actualiza pero la mía no, alguien sale robado. Resolver eso bien —que dos cambios ocurran "todo o nada"— tiene nombre y capítulo propio: se llama transacción, y lo verás en el capítulo 13. De momento, quédate con que sabes registrar el intercambio; hacerlo seguro es el siguiente nivel.
El modelo está completo
Pequeño alto en el camino, porque acabas de cerrar algo grande. Con la amistad y el intercambio, tu base de datos de Kriaturas ya cubre las siete piezas de diseño que se proponía el libro:
- entidad, atributo y clave —
jugador,carta(capítulo 2). - relación 1:N — un jugador, muchos mazos (capítulo 4).
- relación N:M — la colección, los mazos con sus cartas (capítulo 5).
- jerarquía —
cartacomo criatura, entrenamiento u objeto (capítulo 7). - datos históricos — las partidas (capítulo 8).
- atributo derivado — el rango del jugador, calculado a partir de sus victorias (capítulo 8).
- relación reflexiva — amistades e intercambios (este capítulo).
Tienes el plano entero de un juego de cartas: quién juega, con qué cartas, en qué mazos, qué pasa cuando juegan, y cómo se relacionan entre ellos. Si algún día te sientas a programar un juego parecido, esta es, ni más ni menos, la columna vertebral de su base de datos.
Pero "completo" no es lo mismo que "bien hecho". Y ahí es donde miramos a continuación.
Resumen
En este capítulo aprendiste a relacionar una tabla consigo misma. La
amistad es una N:M reflexiva: dos claves foráneas a jugador y una clave
primaria compuesta, con la regla jugador_a < jugador_b para no duplicar parejas
simétricas. El intercambio suma a esa idea reflexiva una carta y una fecha,
juntando en una tabla la reflexividad, las claves foráneas múltiples y el dato
histórico.
Con esto, el modelo de Kriaturas queda completo: las siete piezas de diseño están puestas. Tienes jugadores, cartas, mazos, colecciones, partidas, amistades e intercambios.
La pregunta que abre el siguiente capítulo es incómoda pero necesaria: ¿está bien hecho todo esto? ¿Hay datos repetidos escondidos que algún día te van a dar un disgusto? Toca ordenar la casa. Eso es la normalización, y es lo que viene en el capítulo 10.