Centralia

Núcleo SaaS multitenant

Deja de construir
lo mismo cada vez

Identidad, organizaciones, planes, cobros con Stripe, control de consumo y claves de API. Todo lo que hay debajo de cualquier SaaS, resuelto una sola vez.

Tus productos dejan de gestionar usuarios y límites. Le preguntan al núcleo si pueden hacer algo, lo hacen, y le dicen qué han hecho. El precio lo pone Centralia.

  • PHP 8.4
  • Symfony 7.3
  • Stripe
  • Multitenant
POST /api/servicios/document-chat/consumir
# El servicio declara qué ha hecho
{ "cantidad": 12000 }

# El núcleo decide cuánto vale
{
  "units":      24,
  "restantes":  49976,
  "lotes":      ["plan"],
  "registro":   "apuntado"
}

12 lotes de 1.000 tokens × 2 Units. Si el servicio manda su propio precio, se ignora.

  • 1 moneda común para toda la plataforma
  • 0 líneas de facturación en tus servicios
  • 21 servicios del núcleo, cada uno con un solo trabajo

Qué resuelve

Seis cosas que ya no vuelves a escribir

Un lector de facturas, un chat documental y un generador de informes no tienen nada en común, salvo todo lo que hay debajo. Centralia es exactamente esa parte.

Identidad

Registro, acceso, recuperación de contraseña y roles. Con límite de intentos desde el primer día.

Organizaciones

Cada cliente es un tenant con sus miembros y sus roles. Una persona puede pertenecer a varios y cambiar de uno a otro.

Planes y cobros

Suscripciones con Stripe, portal del cliente, cambios de plan, cancelación y reanudación. Los webhooks entran por una bandeja que impide cobrar dos veces lo mismo.

Control de consumo

Cada operación gasta Units y deja una línea que nadie puede modificar después. Cuando no hay saldo, la puerta se cierra sola.

Claves de API

El tenant crea, revoca y reactiva sus claves. De la clave solo se guarda el prefijo y un hash, y el secreto se enseña una vez.

Aislamiento

El tenant se resuelve siempre desde quien se ha autenticado. Un identificador ajeno no da error de permisos: sencillamente no existe.

La moneda común

Todo se mide en Units

Un servicio cuenta páginas, otro cuenta tokens y otro cuenta peticiones. Para el cliente todo eso es lo mismo: Units de su saldo. Un único valor de la Unit para toda la plataforma, y a partir de ahí salen las cuentas solas.

Lo máximo que puede costarte una Unit

valor de la Unit × (100 − margen) ÷ 100

Lo que cobras por una operación

coste real del lote ÷ coste máximo por Unit

Las Units que da un plan

precio del plan ÷ valor de la Unit

Con la Unit a 0,001 €, un plan de 50 € son 50.000 Units. Es la única cifra que tienes que decidir para que todo lo demás se calcule.

Políticas de margen

Un OCR barato y un modelo caro no aguantan lo mismo

Con un único margen tendrías que elegir entre perder dinero en unos servicios o quedar caro en otros. Por eso las políticas son varias, y cada servicio usa la suya o la marcada por defecto.

Ejemplo con la Unit a 0,001 €. Lo único que necesitas saber es lo que te cuesta la operación.
Servicio Política Te cuesta Cobras Ganas Margen
invoice-reader Estándar 60 % 0,0040 € / página 10 Units 0,0060 € 60 %
document-chat IA intensiva 35 % 0,0008 € / 1.000 tokens 2 Units 0,0012 € 60 %
pdf-to-excel Estándar 60 % 0,0012 € / página 3 Units 0,0018 € 60 %
email-classifier Premium 80 % 0,0002 € / petición 1 Unit 0,0008 € 80 %

Subes el margen de una política

El cliente paga lo mismo, pero esas operaciones le cuestan más Units. Ganas más por operación y su saldo dura menos.

Subes el valor de la Unit

El cliente recibe menos Units por el mismo precio de plan. Ganas más por cada plan vendido, en toda la plataforma a la vez.

La pantalla de tarifas trae una calculadora que simula las dos cosas sin guardar nada, y te enseña los puntos de margen extra que te regala el redondeo.

Dónde vive el saldo

Lotes con caducidad, no un contador mensual

Un contador por mes funciona hasta que vendes la primera recarga. Una recarga no pertenece a ningún mes, así que con un contador o no existiría o caducaría el día 1.

Lote del plan

50.000 Units

Caduca al renovar el mes

32.000 restantes

Lote de recarga

20.000 Units

Sin caducidad

20.000 restantes

El saldo del tenant es la suma de lo que queda en sus lotes vigentes. Un consumo puede pagarse entre varios, y siempre se gasta antes lo que caduca antes: primero el plan, después las recargas.

De cada Unit consumida queda apuntado de qué lote salió. La suma de esas imputaciones es exactamente el consumo, así que las cuentas cuadran solas.

Las Units del plan que sobran se pierden al renovar. Es lo estándar en SaaS, hace predecible tu coste y evita que un cliente acumule seis meses para gastarlos de golpe.

Cómo se conecta un servicio

Tu producto no sabe nada de dinero

No consulta planes, no mira suscripciones y no calcula precios. Pregunta, trabaja y declara. El resto ocurre en el núcleo.

  1. 1

    Das de alta el servicio

    Un slug, una descripción y qué te cuesta una operación. Eliges su política de margen o dejas la de por defecto.

  2. 2

    Lo metes en los planes que quieras

    Un plan es una lista de servicios y unas Units incluidas. Eso responde a qué puede usar cada cliente.

  3. 3

    El servicio llama con la clave del tenant

    Una sola llamada comprueba el acceso y apunta el consumo. Si no hay saldo o el plan no lo incluye, responde que no y no escribe nada.

Autenticación y consumo
# La clave identifica al tenant. Nunca se manda un tenantId.
X-API-Key: plat_a1b2c3d4...

# Comprobar sin gastar nada
GET  /api/servicios/invoice-reader/comprobar

# Trabajar y declarar lo hecho
POST /api/servicios/invoice-reader/consumir
{ "cantidad": 7 }        // 7 páginas

Si un servicio no tiene tarifa configurada, puede declarar él mismo las Units. En cuanto se la pones, el precio deja de estar en sus manos.

Un ejemplo montado

Así queda un catálogo real

Tres planes públicos y uno que no aparece en el catálogo de nadie, porque los acuerdos a medida solo los ve quien los tiene contratados.

Free

0 €

1.000 Units al mes

Para probar la plataforma sin coste.

  • invoice-reader

El del ejemplo

Pro

50 €

50.000 Units al mes

Para equipos que ya tienen volumen recurrente.

  • invoice-reader
  • document-chat

Business

200 €

200.000 Units al mes

Catálogo completo y volumen alto de Units.

  • invoice-reader
  • document-chat
  • pdf-to-excel

No sale en el catálogo

Acuerdo a medida

Negociado

500.000 Units al mes

Solo lo ve el cliente que lo tiene contratado.

  • Todo el catálogo

Qué ganas con el plan Pro, según lo que gaste el cliente

  • No consume nada 50,00 €
  • Lo agota en el servicio al 60 % 30,00 €
  • Lo agota en el servicio al 35 % 17,50 €

Mezclar márgenes convierte el beneficio en un rango, y la tabla de planes lo enseña para que la decisión sea consciente. Casi ningún cliente agota su saldo, así que lo normal es ganar más que el mínimo.

Lo que no se puede romper

Las partes donde un fallo cuesta dinero

Cobrar dos veces, gastar dos veces las mismas Units o ver los datos de otro cliente no son errores que se arreglen después. Estas cuatro tienen prueba escrita.

Dos consumos a la vez

Al registrar un consumo se bloquea la fila del periodo y las de los lotes, siempre en el mismo orden. Dos peticiones simultáneas se ponen en fila: no se puede pasar del límite ni gastar dos veces las mismas Units.

Probado con procesos reales en paralelo

Dos renovaciones a la vez

En cada renovación varias peticiones del mismo cliente intentan abrir el periodo. Se bloquea la fila del tenant antes de crearlo, así que quien pierde la carrera se encuentra el periodo ya hecho.

Probado con procesos reales en paralelo

Un pago que llega dos veces

Stripe reintenta sus webhooks. Cada evento se guarda en una bandeja antes de aplicarse, con la firma verificada contra el cuerpo original, y el repetido se reconoce y se descarta.

Bandeja de eventos con estado

Datos de otro cliente

El tenant sale de la identidad autenticada y nunca de un parámetro. Los recursos se buscan por identificador y tenant a la vez, de modo que un identificador ajeno no existe para ese usuario.

Filtrado en el repositorio, no en el controlador

Y además

  • Claves de API guardadas como prefijo y HMAC-SHA256, con su propia pimienta para poder rotar el secreto de la aplicación sin invalidarlas.
  • Credenciales de Stripe y de los proveedores de IA cifradas en base de datos y rotables desde el panel, sin desplegar.
  • Límite de peticiones por clave, por intento de acceso y por alta desde una misma IP.
  • Protección contra el envío de campos que no tocan: los formularios exponen solo lo editable, y los roles nunca se mapean.

Ficha técnica

Datos del proyecto

Qué es
Núcleo SaaS multitenant sobre el que conectar varios productos
Lenguaje
PHP 8.4
Framework
Symfony 7.3 con Doctrine ORM 3
Base de datos
MySQL 8.0
Pagos
Stripe, con suscripciones, portal del cliente y recargas sueltas
Arquitectura
Symfony convencional: controladores, entidades, repositorios y servicios. Sin capas añadidas
Administración
Panel propio para tarifas, márgenes, credenciales y tenants
Estado
En desarrollo. Núcleo funcionando con productos conectados encima

El servicio dice qué ha hecho.
Centralia dice cuánto vale.

Núcleo SaaS multitenant