Andres Apolinar
ENHablemos
Volver a sistemas
TelecomunicacionesCTO2022–2026

Xpace — La plataforma detrás de 20 operadores móviles

Como CTO, dirigí la plataforma de gestión MVNO que llegó a operar 20 operadores y más de 300 mil usuarios en producción: activación de SIM, recargas, portabilidad, CFDI y validación de identidad regulatoria.

  • Laravel
  • PHP
  • MySQL
  • OpenAI Vision
  • Stripe
  • OpenPay
  • MercadoPago
  • Docker

Ver el sistema(se abre en una pestaña nueva)

Read this case in English

Restricciones

  • Regulatorio con fecha: los Lineamientos del DOF vigentes desde enero de 2026 obligan a vincular cada línea a una persona con identidad verificada.
  • Veinte operadores comparten una plataforma y cada uno responde por sus propios datos.
  • Las recargas y comisiones mueven dinero. Una acreditación duplicada es una fuga, no un bug.
  • La validación de identidad tiene que sostenerse ante una auditoría de operador, no solo funcionar.
  • Cada validación consume una llamada de visión y una consulta de CURP. El costo es real.

El problema

La escala

Xpace es el sistema que opera un negocio de operadores móviles virtuales: empresas que venden líneas sobre la red de un operador grande, con marca propia y sin infraestructura de red propia.

Todo lo que eso implica pasa por una plataforma: activar la SIM, vincularla a una persona verificada, recargar saldo, portar un número, timbrar la factura, pagarle comisión al vendedor, y responder ante el operador cuando pregunta.

Llegó a operar 20 operadores MVNO y más de 300 mil usuarios en producción, con clientes como Metrocel, Beneleit, WIWI y Wiki Katat.

Y ahí está el verdadero problema de ingeniería: no es un sistema con veinte clientes. Es un sistema con un modelo de datos compartido por veinte negocios que compiten entre sí, donde un error de aislamiento no es un bug, es una filtración y una llamada del cliente.

Lo que hace difícil este dominio

Regulación con fecha de entrada en vigor. Los Lineamientos del DOF vigentes desde enero de 2026 obligan a vincular cada línea a una persona con identidad verificada contra documento oficial. No es opcional y no se negocia: si el proceso se salta, quien paga es el operador, con su concesión.

Dinero en tres direcciones. El usuario recarga, el vendedor gana comisión, la plataforma liquida. Cada una es una fuente de acreditaciones duplicadas, y el error no hace ruido hasta que alguien cuadra el mes.

Veinte dueños de sus datos. El aislamiento entre operadores no es una decisión de arquitectura abstracta: es la promesa comercial.

La validación de identidad, que es donde más se ve el criterio

Todo flujo productivo —vinculación del cliente, validación del operador, ventas, APIs de activación y recarga— pasa por un único orquestador. Nueve compuertas en cadena:

sube frente y reverso del INE
        │
        ▼
 0  Compuerta determinista de color (GD)    → fotocopia o B/N: rechazo, costo $0
 1  Análisis de visión (frente + reverso)   → salida atada a json_schema estricto
 2  Calidad de imagen                       → CURP ilegible: rechazo
 3  Presencia física                        → cualquier reproducción: rechazo
 4  Consistencia de identidad               → personas distintas: rechazo
 4b Integridad del documento                → anomalía crítica o alta: rechazo
 4c Integridad de la foto del titular       → capa CONFIRMADA: rechazo · duda: aviso
 5  Misma credencial                        → frente y reverso del mismo documento
 6  CURP capturado vs CURP del INE          → coincidencia
 7  Estatus oficial de la CURP              → clave no vigente: rechazo
 8  Segunda pasada adversarial              → solo si todo lo anterior aprobaría
        │
        ▼
  is_identity_validated = true   (la imagen NO se guarda)

Dos cosas de ese diseño merecen atención.

La compuerta 0. Se decide con aritmética sobre los bytes de la imagen antes de gastar un solo token. Elimina la clase de fraude más frecuente —la fotocopia, la captura desaturada— en milisegundos y a costo cero. Es la negación de “todo pasa por el modelo”: lo que se puede decidir barato se decide barato.

La compuerta 8. Si las ocho anteriores aprobarían, se hace una segunda pasada con un prompt adversarial que asume que el modelo se dejó engañar. Solo puede rechazar ante evidencia confirmada, nunca ante duda. Se paga únicamente en las credenciales que están a punto de pasar, o sea donde vive el riesgo real de falso positivo.

Y una regla que no es técnica: la credencial no se guarda. El resultado es un booleano y un estado. La imagen del INE no se persiste. Se cumple el objetivo del sistema sin convertirse en custodio de un documento que no se necesita.

Lo que se paga por ser estricto

Hay dos cosas que en este proyecto se hicieron más difíciles a propósito, y vale decirlas.

La API de integradores se reescribió desde cero. La versión anterior tenía una sola clave global en el archivo de entorno mapeada a operadores escritos a mano en el código. No se podía dar de alta un integrador nuevo sin tocar el .env y el código. La nueva usa llaves con prefijo visible para búsqueda indexada, hash para la comparación y revocación, formato xp_{live|test}_{48 hex}.

El suite de tests está frenado por un guard explícito. Los tests que tocan base de datos requieren una variable de entorno y una base aislada, y el archivo de configuración fuerza el nombre de esa base con un comentario que dice que los tests nunca deben correr contra desarrollo. Es protección deliberada contra destruir datos reales — y al mismo tiempo significa que en la práctica hay tests que no corren por defecto.

Los dos hechos son verdad y los dos están publicados. Un sistema que solo cuenta sus virtudes no es un sistema documentado; es un folleto.

Sobre el equipo

Dirigí esta plataforma como CTO. El modelo de datos, la arquitectura y las reglas del dominio son diseño mío; la implementación de los módulos fue de mi equipo, y el trabajo pasó por revisión con Pull Requests. Los números —158 migraciones, 96 modelos, 171 controladores— describen la magnitud de lo que se operó, no una suma de commits.

Y el documento de auditoría de identidad existe por una razón que no es técnica: un sistema de validación que solo vive en la cabeza de quien lo escribió no pasa una auditoría. La documentación fue parte del entregable.

Decisiones

  1. 01

    El ambiente lo decide la credencial, no el request

    En la primera versión de la API para integradores el modo sandbox era un parámetro del request, revisado con condicionales repartidos por cada método, y en varios flujos el sandbox sí escribía en la base. La corrección fue mover la decisión al sujeto autenticado: una llave sandbox jamás puede tocar producción y viceversa, y no existe un flag manipulable.

    DescartadoSeguir parchando los condicionales. Mientras el modo sea un dato de entrada, cualquiera puede mentir sobre él.

  2. 02

    Sandbox con cero escrituras, incluso en la telemetría

    En ambiente sandbox no se ejecuta ninguna escritura ni se llama al proveedor. Eso obligó a renunciar a algo que cualquiera pondría sin pensar: la marca de último uso de la llave se actualiza solo en producción, porque hasta registrar que te autenticaste es una escritura. Es el costo de que la garantía de cero efectos sea literal y verificable.

  3. 03

    Sandbox determinista por semilla, no aleatorio

    El simulador siembra sus respuestas con un hash del payload, así que la misma petición produce siempre el mismo identificador de línea. Eso hace que los tests sean reproducibles y las aserciones estables.

    DescartadoDatos aleatorios, que hacen los tests no deterministas, y datos fijos por operación, que no distinguen un payload de otro.

  4. 04

    Exactly-once por restricción de base de datos, no por lógica

    El bono mensual de referidos es idempotente por un índice único sobre cliente y mes. Tres puntos de entrada distintos podían disparar el crédito, y la restricción es lo único que sobrevive a una carrera entre procesos. La violación se captura como caso esperado, no como error.

    DescartadoUn condicional que consulte si ya existe el bono. Entre la consulta y la inserción cabe otra petición.

  5. 05

    Las mutaciones de saldo van con bloqueo explícito

    Toda mutación de wallet corre dentro de una transacción con bloqueo de lectura sobre la fila. El riesgo estaba documentado como conocido porque no es teórico: una recarga concurrente con el procesamiento mensual toca el mismo saldo.

  6. 06

    La identidad es de la LÍNEA, no del cliente

    La versión anterior guardaba la CURP en el cliente. Se movió a una tabla de identidad por línea, y la desvinculación es terminal: una línea desvinculada no vuelve a enlazarse por flujos automáticos. El análisis de impacto está escrito y cuantificado antes del cambio —573 filas, unos treinta sitios escritores en quince archivos, y los gates de lectura de cada controlador con número de línea—.

    DescartadoMantener la CURP en el cliente porque "ya funcionaba". El problema es que un cliente con cinco líneas tiene cinco identidades, y el modelo no lo representaba.

  7. 07

    La compuerta barata antes del modelo de visión

    Antes de mandar nada a la API de visión, una comprobación determinista mide el croma de la imagen y rechaza credenciales en blanco y negro o fotocopias. Corre incluso en sandbox porque no cuesta nada. Lo que se puede decidir con aritmética se decide con aritmética: es gratis, determinista y auditable.

  8. 08

    Ser fail-closed en la evidencia y tolerante en la duda

    El sistema rechaza ante cualquier evidencia confirmada —capa sobre la foto del titular, alteración crítica— pero solo emite aviso cuando el modelo dice no saber. Un reflejo de laminado en una credencial real produce duda, y la duda no puede costarle la línea a un cliente legítimo. La asimetría es deliberada y está calibrada tras falsos rechazos reales.

    DescartadoRechazar ante cualquier nivel de confianza. Es más seguro en papel y arruina la conversión en producción.

Resultado

  • 20 operadores MVNO operando sobre la plataforma, con más de 300 mil usuarios en producción.
  • Clientes como Metrocel, Beneleit, WIWI y Wiki Katat.
  • 158 migraciones, 96 modelos y 171 controladores sobre un dominio de telecomunicaciones completo.
  • Un documento de auditoría de identidad que describe las nueve compuertas, los criterios oficiales de INE y Registro Civil, y el rol de cada archivo — escrito para sostener la decisión ante un auditor de operador.
  • API de integradores reconstruida con llaves por prefijo visible, hash y revocación, en lugar de una sola clave global en el archivo de entorno.