SISTEMA
REKIUM
Plataforma de Automatización Corporativa
Sobre este sistema
Sistema Rekium es una plataforma de automatización integral construida desde cero para gestionar el ciclo de vida completo de los miembros de una corporación de EVE Online: desde el primer contacto en Discord hasta la auditoría con IA, pasando por reclutamiento, gestión de personajes, flotas, timers, tickets internos y dashboards analíticos. Todo el sistema usa Discord como interfaz principal — el usuario nunca abandona la app — mientras que el backend orquesta más de una docena de workflows en n8n contra Alliance Auth, Baserow y MariaDB.
Esta documentación describe la arquitectura, los flujos y las decisiones técnicas tomadas. Sirve también como muestra de mi trabajo: arquitectura distribuida, integración entre cinco plataformas heterogéneas, ingeniería de prompts y de errores, decisiones de coste cero y patrones reutilizables.
Changelog · Cambios v2.1 → v3.0
HTTP Request PATCH con user_field_names=true. El nodo nativo enviaba enteros en vez del array [id] que requieren los campos Link, fallando silenciosamente. Extendido también el flujo para hacer split de todos_los_personajes (string CSV de Alliance Auth) y crear/actualizar cada personaje individualmente en la tabla Personajes con Merge de reconvergencia.
ID Discord en la tabla Reclutamiento se auto-puebla al escribirse correctamente el campo Link.
opcalendar con LAST_INSERT_ID() + embed con botones de asistencia. Categorías: PVP, ORE, ICE, Lunar, Gas, Rateo. Eventos forzados a usuario superadmin para evitar problemas de permisos; FC almacenado en campo fc.
opcalendar con operation_type_id=14 (Estructuras PVP). FCs introducen timers desde Discord sin necesidad de abrir Alliance Auth.
corptools_characteraudit.active con la lista de miembros y reporta personajes sin token activo. Implementado chunking para sortear el límite de 4000 caracteres de Discord.
GROUP_CONCAT para todas las corps, sincroniza tablas Main, Personajes y Discord, y publica al canal de seguridad un resumen de altas detectadas. Filtro date_equal sobre campo Created on para identificar nuevos miembros.
token.refresh(), evitando pedir re-registro a 200+ miembros.
cerrar_ticket ya disponible desde el primer mensaje, no esperando a fase de verificación.
noble (Ubuntu 24.04). MariaDB en 10.11.x estable. Migración a 11.8 prevista en ventana única coincidiendo con el lanzamiento de Alliance Auth v5.
Capacidades del Sistema
GROUP_CONCAT, en tiempo real.Stack Tecnológico
Toda la infraestructura es self-hosted en NAS y VPS propios. Cero dependencia de SaaS pago. La elección está pensada para minimizar coste recurrente sin sacrificar capacidad de orquestación ni almacenamiento estructurado.
Arquitectura de Enrutado (W0 Router)
Un único punto de entrada procesa todas las interacciones de Discord. El Cloudflare Worker verifica la firma Ed25519 y responde síncronamente para cumplir el SLA de 3 segundos de Discord. W0 es el dispatcher central que evalúa el custom_id y despacha al sub-workflow correcto vía executeWorkflowTrigger sin esperar respuesta. Este patrón sustituyó a 15 webhooks separados en Cloudflare Worker, simplificando enormemente el mantenimiento.
Pipeline de Reclutamiento (W1–W5)
Cinco etapas en cadena con tres niveles de aprobación y tres ramas de archivo. Diseñado para que el candidato no abandone Discord en ningún momento mientras la corporación mantiene trazabilidad completa: cada paso queda registrado en Baserow y cada decisión queda firmada por su staff correspondiente.
-
W1Crear Ticket — Anti-duplicados nativoAntes de crear el canal busca en la tabla de Reclutamiento si ya existe un ticket abierto para ese usuario (campo Fecha cierre vacío). Si existe, responde efímero con
flags: 64sin crear canal nuevo. Si no, crea canal privado con permisos de roles y envía mensaje de bienvenida que ya incluye el botóncerrar_ticketdesde el primer momento. -
W2Verificar Personaje — Main + todos los altsQuery MariaDB con JOIN a través de
authentication_userprofile.main_character_idpara garantizar que se devuelve el personaje principal, no un alt.GROUP_CONCATrecupera en una sola fila todos los personajes vinculados a la cuenta. Escritura en Baserow vía HTTP PATCH (no nodo nativo) conuser_field_names=truey arrays correctos para los campos Link. Splitting de la lista de personajes y creación/actualización de cada uno en la tabla Personajes. -
W3Formulario de Solicitud — Modal DiscordAbre un modal de Discord con campos personalizados. El Cloudflare Worker devuelve
type: 9síncronamente — esto no se puede delegar a n8n por el SLA de Discord. Las respuestas se guardan en la tabla de Reclutamiento y se notifica al canal del ticket. -
W4Aprobación — Cadena de tres etapasVotación escalonada: Reclutadores → Asuntos Internos → Directores. Cada etapa solo desbloquea la siguiente cuando se aprueba. Informe de auditoría auto-publicado en el canal permanente de Asuntos Internos. W4b se activa opcionalmente para checklist interactivo con resumen del candidato extraído de MariaDB.
-
W4bAuditoría IA — Checklist interactivoGenera informe automático del candidato a partir de datos de MariaDB (ISK, SP, kills, capital ships). Los revisores completan un checklist interactivo en Discord desde el canal de Asuntos Internos. La publicación es permanente para auditoría histórica.
-
W5Cierre — Tres ramas paralelas según tipo de ticketAl pulsar el botón de cierre, W5 genera transcripción y captura. La imagen se sube vía HTTP PATCH con
JSON.stringify(el nodo nativo Baserow falla con campos de archivo — doble-serializa). Tres ramas paralelas actualizan distintas tablas según origen: Reclutamiento, Director (estado → «Archivado»), IT/Soporte (estado → «Archivado»). El canal de Discord se elimina al finalizar.
Gestión de Personajes Alternativos (A1 / A2)
Los miembros registran personajes alternativos a través de Discord. A1 guarda el ID del ticket en la tabla Claim — campo crítico para que A2 encuentre la solicitud al aprobar o rechazar. A2 incluye una cadena completa de Claim linking en ambas ramas, garantizando trazabilidad bidireccional.
1. Buscar Discord director en tabla Discord
2. HTTP GET a Claim filtrando por Main link row ID
3. Actualizar el ticket Director correspondiente
Notificación al miembro con resultado.
Sistema de Tickets IT y Director
Ambos tipos de ticket incluyen menú desplegable de selección de tipo antes de crear el canal, sistema de claim para asignación de staff, y se archivan en W5 actualizando estado a «Archivado» en sus respectivas tablas.
Roles: Rekium IT
Un IF node distingue si la interacción es un nuevo claim de staff o una selección de tipo, manteniendo IT1 e IT1b como un único punto de entrada.
Roles: Director, CEO
Cadena completa de Claim linking: Discord → Claim filtrando por Main row ID → actualización de Director Tickets.
Operaciones de Flota (F1 / F2)
F1 abre un modal Discord con tipo de operación predefinido. F2 procesa la respuesta, hace INSERT en la tabla opcalendar de Alliance Auth con LAST_INSERT_ID() para recuperar el ID del evento, y publica un embed con botones de asistencia. Categorías soportadas: PVP, ORE, ICE, Lunar, Gas, Rateo. Los eventos se fuerzan al usuario superadmin para evitar problemas de permisos; el FC real queda almacenado en el campo fc.
Como las interacciones de Discord no encadenan estado entre sí, el
custom_id del modal codifica el tipo de operación seleccionado en F1 — única manera de que F2 sepa qué categoría asignar al evento.
Sistema de Structure Timers (ST1)
Permite a los FCs introducir timers de estructuras directamente desde Discord, sin necesidad de abrir Alliance Auth. Un modal recoge los datos del timer; ST1 publica un mensaje en el canal PVP y simultáneamente inserta el evento en opcalendar con la categoría adecuada — quedando integrado con el resto del calendario corporativo.
-
1Trigger desde modal DiscordEl FC pulsa un botón en el canal de timers. Cloudflare Worker responde con
type: 9y abre un modal con campos: estructura, sistema, fecha/hora, tipo de timer (armor/hull/anchor). -
2Validación y publicaciónST1 valida el formato de fecha (debe ser
YYYY-MM-DDestricto, sin prefijo=) y publica un embed enriquecido en el canal PVP con countdown. -
3Inserción en opcalendar
INSERTdirecto en MariaDB con categoría «Estructuras PVP» y visibilidad PVP. El evento aparece automáticamente en el calendario corporativo del Auth y se dispara el resto de integraciones aguas abajo.
Workflows de Mantenimiento (M1 / M2 / M3)
Tres workflows programados que mantienen el ecosistema saneado sin intervención manual: auditoría de tokens ESI, recordatorios de flota y sincronización semanal de miembros entre Alliance Auth y Baserow.
corptools_characteraudit.active con miembros activos. Reporta personajes sin token válido. Mensajes >4000 chars se chunkan automáticamente.
GROUP_CONCAT sobre las dos corps de la alianza, sincroniza Main + Personajes + Discord, publica resumen de altas en canal de seguridad.
El filtro
date_equal sobre el campo Created on de Baserow requiere usar la API directa, ya que el nodo nativo de n8n no soporta filtros sobre tipos created_on. Implementado vía HTTP Request GET con query string filter__created_on__date_equal.
Dashboards Analíticos · Grafana
Grafana conectado directamente a MariaDB de Alliance Auth (read-only) provee dashboards en vivo sobre la actividad de la corporación. El primero ya está en producción; los siguientes están en construcción a medida que se valida el esquema real de cada módulo.
| DASHBOARD | ESTADO | FUENTES | VISUALIZACIONES |
|---|---|---|---|
| PvP | Producción | killtracker_* | KPIs · Timeline kills/losses · Top Killers · Top Losses |
| Finanzas | En diseño | wallet · bounties | ISK por miembro · Bounties acumulados · Series temporales |
| Minería | En diseño | moonmining_* | Leaderboard mensual · Histórico extracción · Composición de ore |
| Auditoría | En diseño | characteraudit · userprofile | Charlinks fallidos · Miembros sin actividad · Tokens vencidos |
Estructura de Datos Baserow
Siete tablas con relaciones bidireccionales modelan todo el dominio. Las relaciones se mantienen mediante campos Link y Lookup; las escrituras de Link usan arrays de IDs y nunca enteros sueltos — el nodo nativo de Baserow falla silenciosamente con esto.
Los campos Lookup son de solo lectura. Incluirlos en operaciones
PATCH o PUT produce errores 400. Siempre referenciarlos solo en lectura.
Roles y Permisos Discord
| ROL | ACCESO | FUNCIONES PRINCIPALES |
|---|---|---|
| CEO | Total | Acceso completo a todos los tickets y configuración del sistema |
| Director | Alto | Tickets de dirección, aprobación final de reclutamiento, cierre de tickets |
| Reclutadores | Medio | Pipeline W1–W5, primera votación de candidatos, cierre de tickets propios |
| Asuntos Internos | Medio | Notificaciones de aprobación intermedia, W4b auditoría con IA, canal permanente |
| Rekium IT | Técnico | Tickets IT/Soporte, claim de tickets técnicos |
| Miembros | Básico | Abrir tickets IT/Director, registrar alts, responder a flotas |
Inventario Completo de Workflows n8n
| WORKFLOW | TRIGGER | DESCRIPCIÓN | ESTADO |
|---|---|---|---|
| W0 Router | Webhook (todas) | Dispatcher central — parsea custom_id y despacha a todos los sub-workflows | Producción |
| W1 | Botón: solicitud | Anti-duplicados + canal privado + bienvenida con botón cerrar incluido | Producción |
| W2 | Botón: verificar | Main + GROUP_CONCAT alts vía MariaDB · HTTP PATCH a Baserow · split de personajes | Producción |
| W3 | Botón: formulario | Modal Discord (type:9 desde CF Worker) → guarda en Reclutamiento | Producción |
| W4 | Botón: aprobar/rechazar | Votación 3 etapas + informe auditoría → canal Asuntos Internos | Producción |
| W4b | Botón: auditoría | Checklist interactivo + informe IA con datos MariaDB | Validando |
| W5 | Botón: cerrar ticket | Transcript + imagen vía HTTP PATCH · 3 ramas paralelas de archivo | Producción |
| A1 | Botón: registrar alt | Verify Alliance Auth + Claim con ID de ticket Discord guardado | Producción |
| A2 | Botón: claim alt | Claim linking chain en ambas ramas (aprobación y rechazo) | Producción |
| IT1 + IT1b | Select menú + claim | Ticket IT con tipo desde menú · IF node distingue claim vs selección | Producción |
| D1b | Select menú + claim | Ticket Director con tipo · Claim linking chain Discord → Claim → Director | Producción |
| F1 / F2 | Select + Modal | Pings de flota · INSERT opcalendar · embed con botones de asistencia | Producción |
| ST1 | Modal: timer | Structure timers en canal PVP + INSERT opcalendar categoría 14 | Producción |
| M1 | Cron: Lun 09:00 | Auditoría semanal de charlinks · chunking automático para Discord | Producción |
| M2 | Cron: cada 15 min | Re-ping de flota en ventana 30–60 min antes del evento | Producción |
| M3 | Cron: Lun 08:00 | Sync semanal de miembros · resumen altas a canal seguridad | Producción |
Lecciones Técnicas Clave
Aprendizajes condensados a lo largo de 18+ meses de iteración en producción. Cada uno surgió de un bug real y una vez identificado dejó de morder.
-
FILECampos de archivo Baserow — HTTP PATCH obligatorioEl nodo nativo Baserow doble-serializa los campos de archivo y los rompe sin error visible. Siempre usar HTTP PATCH con
user_field_names=trueyJSON.stringifyen el body. Nunca usar el nodo Baserow para campos de tipo file. -
LINKCampos Link — arrays de IDs, nunca enterosEl nodo nativo Baserow envía enteros para campos Link. Baserow espera arrays
[id]. La escritura falla silenciosamente: el row se actualiza, pero el campo Link queda vacío. Solución:HTTP PATCHconuser_field_names=trueenviando arrays explícitamente. -
SQLMariaDB — JOIN correcto para personaje principalEl JOIN debe pasar por
authentication_userprofile.main_character_idpara garantizar que se devuelve el personaje principal y no un alt cualquiera.GROUP_CONCATes esencial para evitar problemas de múltiples filas en n8n. El campodiscord_discorduser.uidesbiginty requiereCAST(... AS UNSIGNED). -
ACKContexto tras nodo ACK DiscordDespués del nodo ACK en sub-workflows,
$jsonapunta a la respuesta del PATCH, no al payload original. Todos los nodos posteriores deben referenciar$('Parsear body').item.jsonexplícitamente. -
CFModales — Cloudflare Worker responde síncronamenteLa apertura de modales (
type: 9) debe devolverse directamente desde el Worker. No se puede delegar a n8n por el SLA de 3 segundos de Discord. Usarctx.waitUntil()para reenviar el payload a n8n tras responder. -
PERMDiscord — READ_MESSAGE_HISTORY es esencialEl permiso
READ_MESSAGE_HISTORY(65536) es obligatorio para que mensajes ya publicados sean visibles a usuarios que se incorporen al canal. Sin él, los pings de flota no se ven cuando alguien entra después. El allow value correcto es 117760, no 52224. -
FKMariaDB — FK chains complejasPara limpiezas con múltiples constraints encadenadas, el patrón fiable es
SET FOREIGN_KEY_CHECKS = 0 / DELETE / SET FOREIGN_KEY_CHECKS = 1. Más rápido y predecible que reordenar borrados manualmente. -
ESITokens ESI — refresh masivo sin re-registroTokens marcados como expirados a menudo no están revocados, solo necesitan refresh. Iterar en Django shell con
token.refresh()reactiva cientos de tokens sin pedir re-registro a los miembros. El comandoesi_refresh_tokensde management no existe en todas las versiones de Auth. -
SYSMariaDB — Codename del repo debe coincidir con UbuntuEl repo de MariaDB debe usar el codename correcto de Ubuntu (
noblepara 24.04, nojammy). Un mismatch causó que el paquete se desinstalara durante una actualización y el servicio fallara con dependencias delibaio1. -
DISCDiscord — Mensajes >4000 chars requieren chunkingLa API rechaza con 400 cualquier
contentmayor de 4000 caracteres. Para mensajes generados (auditorías, listados de miembros), implementar chunking que respete saltos de línea y separadores semánticos.
Stack de Competencias Demostradas
Resumen de las habilidades técnicas aplicadas en este sistema. Cada bloque representa un dominio donde he ido del «no sé qué es» al «lo tengo en producción y lo mantengo».
- n8n self-hosted en NAS
- 14+ workflows en producción
- Patrón router único + sub-workflows
- Triggers: webhook, cron, manual
- Workflow supervisor con Error Trigger
- Bot custom vía Interactions API
- Botones, select menus, modales
- Verificación Ed25519 en CF Worker
- Embeds, mentions, ephemeral
- Permisos avanzados de canal
- MariaDB 10.11 administración
- SSH tunnel desde n8n
- Queries con JOIN, GROUP_CONCAT, CAST
- Baserow API (HTTP) y nodo nativo
- Esquema relacional con Link/Lookup
- Cloudflare Workers en producción
- Verificación criptográfica
- Routing y respuestas síncronas
ctx.waitUntil()para fetch asíncrono- Coste cero hasta 100k req/día
- Ubuntu 24.04 VPS bare-metal
- Gestión de repos y dependencias
- Synology NAS para self-hosting
- Backups, snapshots y rolling updates
- Django shell para Alliance Auth
- Grafana sobre MariaDB read-only
- Dashboards: PvP, finanzas, minería, auditoría
- KPIs y series temporales
- Alertas y monitorización
- Modelado de queries reutilizables
- Workflow W4b con asistente IA
- Generación de informes automáticos
- Checklist interactivos guiados
- Prompt engineering para precisión
- Integración con datos estructurados
- Arquitectura distribuida 5 plataformas
- Idempotencia y anti-duplicados
- Decisiones coste cero / self-hosted
- Documentación viva del sistema
- Iteración basada en logs y bugs reales
