Saltar al contenido
pcreative Commerce

Cómo habla el panel con el backend

El panel de gestión es una aplicación aparte.

El panel de gestión es una aplicación aparte. No comparte proceso con el backend, no comparte base de datos y no importa su código: le habla por HTTP, como se lo hablaría cualquier otro programa.

Esta página explica por qué está montado así, que es lo que no se deduce leyendo el código.

El token no está en el navegador

Es la decisión de la que cuelga todo lo demás.

Lo habitual en un panel de administración es guardar el token de sesión en localStorage y mandarlo como Authorization: Bearer …. Funciona, es cómodo, y tiene un problema que no se ve hasta que es tarde: cualquier ejecución de JavaScript ajeno en el panel se lo lleva. Una dependencia comprometida, un campo de texto que se pinta sin escapar, una extensión del navegador. Y con ese token se entra desde cualquier sitio, sin contraseña y sin dejar un solo intento fallido en el registro que alguien pueda mirar.

Aquí el token vive en el servidor del propio panel. El navegador solo tiene una cookie:

HttpOnly · Secure · SameSite=Strict · prefijo __Host-

HttpOnly significa que el JavaScript de la página no puede leerla. Un robo de token deja de ser posible porque no hay nada que robar en el navegador.

HttpOnlyel script de la página no la ve
Secureno viaja fuera de HTTPS
SameSite=Strictno se manda en peticiones que vienen de otro sitio, que es lo que corta el CSRF
__Host-el navegador la ata a este dominio exacto y a /; un subdominio no puede sobrescribirla

⚠️ __Host- exige Secure, y el navegador rechaza en silencio una cookie __Host- que no lo lleve. No hay error, no hay aviso: el acceso parece correcto y la sesión no se guarda nunca — entrar, y volver al login. Por eso el prefijo solo se pone cuando la conexión lo admite (localhost, o HTTPS real mirando X-Forwarded-Proto). Si tu proxy no manda esa cabecera, no habrá prefijo, y conviene saberlo antes de perder una tarde.

Las llamadas pasan por el propio panel

Las pantallas no llaman al backend: llaman a /admin/api/backend/*, que es una ruta del panel. Ahí, en el servidor, se lee la cookie, se saca el token y se reenvía la petición.

Esto tiene tres consecuencias que valen más que la comodidad que quita:

  1. Las pantallas del panel no necesitan CORS. El navegador habla con su propio origen. CORS_PANEL sigue existiendo en el backend, para un navegador de otro origen que llame a /gestion directamente; Docker la rellena con PANEL_URL.
  2. El backend no queda expuesto al navegador. Solo el panel le habla.
  3. Los backends están en una lista blanca del servidor. El proxy no reenvía a donde le digan: solo habla con los backends que el panel conoce —PCC_BACKEND_URL y el backendUrl de cada tienda de STORES— y cualquier otra cosa cae en el primero. Un parámetro manipulado no convierte el panel en un puente hacia otra máquina.

El panel del vendedor (apps/vendedores, puerto 7002) sigue el mismo patrón: el token vive en su servidor tras una cookie HttpOnly, y las pantallas llaman a su propio /api/backend/*. Allí la lista es de rutas: solo reenvía los caminos que el panel del vendedor necesita, y a cualquier otro contesta 403. Solo habla con un backend, PCC_BACKEND_URL.

El cliente es nuestro, y por qué

@pcreative/admin-client. Sin dependencias.

Antes había un SDK de terceros y se cambió tras mirar el uso real: de las llamadas del panel, la mitad larga eran una petición HTTP a pelo y el resto se repartía en una treintena de métodos. A cambio de ese envoltorio delgado, decenas de ficheros del panel quedaban atados a una dependencia externa y a su forma de nombrar las cosas.

Lo que sí hubo que replicar exactamente es el formato de la petición, porque eso no es una decisión de diseño: es lo que el backend espera al otro lado.

  • La consulta se serializa con corchetes: filtro[campo]=valor, y las listas indexadas, ids[0]=a&ids[1]=b.
  • Los nulos se omiten.
  • Las cookies viajan.
  • El error lleva el mensaje del backend, no el genérico del código de estado.

Equivocarse en cualquiera de esas cuatro no da ningún error: se manda un filtro que no casa con nada, el backend responde 200 con una lista vacía, y la pantalla dice «no hay productos» con el catálogo lleno. Por eso el cliente lleva pruebas sobre el formato y no sobre que «funcione».

Un panel, varias tiendas

Las tiendas que el panel puede llevar se declaran en STORES (un JSON con, para cada tienda, su backendUrl, storefrontUrl, marca y datos fiscales). Con más de una aparece un selector de tienda en la pantalla de entrada; la elección se guarda en la cookie pcc-tienda y, al recargar, el panel aplica la configuración de esa tienda. Activar un tema no cambia de backend: el tema es lo que enseña el escaparate, no con quién habla el panel.

La sesión es de cada backend, así que al cambiar de tienda hay que volver a entrar. No es un descuido: que una sesión sirviera para varias tiendas convertiría el panel en una llave maestra.

Para desplegarlo

Detrás de un proxy inverso, lo único que no se puede olvidar es X-Forwarded-Proto, por lo dicho arriba. Los puertos y un Caddyfile para toda la tienda están en Instalación.