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.
Qué hace cada pieza de esa cookie
HttpOnly | el script de la página no la ve |
Secure | no viaja fuera de HTTPS |
SameSite=Strict | no 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-exigeSecure, 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 mirandoX-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:
- Las pantallas del panel no necesitan CORS. El navegador habla con su
propio origen.
CORS_PANELsigue existiendo en el backend, para un navegador de otro origen que llame a/gestiondirectamente; Docker la rellena conPANEL_URL. - El backend no queda expuesto al navegador. Solo el panel le habla.
- 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_URLy elbackendUrlde cada tienda deSTORES— 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.