fix: vendedor accede a invitaciones trial (no invitar cliente)

This commit is contained in:
Horux Dev
2026-06-22 23:06:39 +00:00
parent cc002adbd2
commit a1727321c3
4 changed files with 12 additions and 9 deletions

View File

@@ -9,10 +9,10 @@ export async function createInvitation(req: Request, res: Response, next: NextFu
return res.status(400).json({ message: 'El email es requerido' }); return res.status(400).json({ message: 'El email es requerido' });
} }
// Admin y Vendedor (platform_sales) pueden crear invitaciones // Solo platform_admin puede crear invitaciones de cliente
const isAdmin = await hasAnyPlatformRole(req.user!.userId, 'platform_admin', 'platform_sales'); const isAdmin = await hasAnyPlatformRole(req.user!.userId, 'platform_admin');
if (!isAdmin) { if (!isAdmin) {
return res.status(403).json({ message: 'Solo administradores o vendedores pueden crear invitaciones' }); return res.status(403).json({ message: 'Solo administradores pueden crear invitaciones' });
} }
const invitation = await clientInvitationService.createInvitation({ const invitation = await clientInvitationService.createInvitation({
@@ -70,7 +70,7 @@ export async function registerFromInvitation(req: Request, res: Response, next:
export async function resendInvitation(req: Request, res: Response, next: NextFunction) { export async function resendInvitation(req: Request, res: Response, next: NextFunction) {
try { try {
const isAdmin = await hasAnyPlatformRole(req.user!.userId, 'platform_admin', 'platform_sales'); const isAdmin = await hasAnyPlatformRole(req.user!.userId, 'platform_admin');
if (!isAdmin) { if (!isAdmin) {
return res.status(403).json({ message: 'No autorizado' }); return res.status(403).json({ message: 'No autorizado' });
} }
@@ -88,7 +88,7 @@ export async function resendInvitation(req: Request, res: Response, next: NextFu
export async function listInvitations(req: Request, res: Response, next: NextFunction) { export async function listInvitations(req: Request, res: Response, next: NextFunction) {
try { try {
const isAdmin = await hasAnyPlatformRole(req.user!.userId, 'platform_admin', 'platform_sales'); const isAdmin = await hasAnyPlatformRole(req.user!.userId, 'platform_admin');
if (!isAdmin) { if (!isAdmin) {
return res.status(403).json({ message: 'No autorizado' }); return res.status(403).json({ message: 'No autorizado' });
} }

View File

@@ -3,6 +3,7 @@ import { z } from 'zod';
import * as tenantsService from '../services/tenants.service.js'; import * as tenantsService from '../services/tenants.service.js';
import { AppError } from '../middlewares/error.middleware.js'; import { AppError } from '../middlewares/error.middleware.js';
import { isGlobalAdmin } from '../utils/global-admin.js'; import { isGlobalAdmin } from '../utils/global-admin.js';
import { hasAnyPlatformRole } from '../utils/platform-admin.js';
import { isOwnerSomewhere } from '../utils/memberships.js'; import { isOwnerSomewhere } from '../utils/memberships.js';
async function requireGlobalAdmin(req: Request): Promise<void> { async function requireGlobalAdmin(req: Request): Promise<void> {
@@ -13,8 +14,10 @@ async function requireGlobalAdmin(req: Request): Promise<void> {
export async function getAllTenants(req: Request, res: Response, next: NextFunction) { export async function getAllTenants(req: Request, res: Response, next: NextFunction) {
try { try {
const isAdmin = await isGlobalAdmin(req.user!.tenantId, req.user!.role, req.user!.userId); // Admin global, TI y Vendedor pueden ver el listado completo de tenants.
if (!isAdmin) { // Vendedor lo necesita para enviar invitaciones de trial.
const canList = await hasAnyPlatformRole(req.user!.userId, 'platform_admin', 'platform_ti', 'platform_sales');
if (!canList) {
// Evita 403 en consola del frontend cuando componentes sin-gate hacen polling // Evita 403 en consola del frontend cuando componentes sin-gate hacen polling
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
return res.json([]); return res.json([]);

View File

@@ -12,7 +12,7 @@ const ROLE_META: Record<PlatformRole, { label: string; desc: string; icon: any;
platform_admin: { label: 'Admin', desc: 'Todo: gestión staff, precios, clientes, facturas', icon: ShieldCheck, color: 'bg-red-100 text-red-700 border-red-200' }, platform_admin: { label: 'Admin', desc: 'Todo: gestión staff, precios, clientes, facturas', icon: ShieldCheck, color: 'bg-red-100 text-red-700 border-red-200' },
platform_ti: { label: 'TI', desc: 'Equipo de TI. Mismos permisos que Admin (diferencia solo en trazabilidad)', icon: Cpu, color: 'bg-slate-100 text-slate-700 border-slate-200' }, platform_ti: { label: 'TI', desc: 'Equipo de TI. Mismos permisos que Admin (diferencia solo en trazabilidad)', icon: Cpu, color: 'bg-slate-100 text-slate-700 border-slate-200' },
platform_support: { label: 'Support', desc: 'Ver tenants, resolver tickets', icon: HeadphonesIcon, color: 'bg-blue-100 text-blue-700 border-blue-200' }, platform_support: { label: 'Support', desc: 'Ver tenants, resolver tickets', icon: HeadphonesIcon, color: 'bg-blue-100 text-blue-700 border-blue-200' },
platform_sales: { label: 'Vendedor', desc: 'Enviar invitaciones a nuevos despachos', icon: TrendingUp, color: 'bg-green-100 text-green-700 border-green-200' }, platform_sales: { label: 'Vendedor', desc: 'Enviar invitaciones de trial', icon: TrendingUp, color: 'bg-green-100 text-green-700 border-green-200' },
platform_finance: { label: 'Finance', desc: 'Pagos, facturas manuales, editar precios', icon: DollarSign, color: 'bg-amber-100 text-amber-700 border-amber-200' }, platform_finance: { label: 'Finance', desc: 'Pagos, facturas manuales, editar precios', icon: DollarSign, color: 'bg-amber-100 text-amber-700 border-amber-200' },
}; };

View File

@@ -73,7 +73,7 @@ const adminNavigation: NavItem[] = [
]; ];
const vendedorNavigation: NavItem[] = [ const vendedorNavigation: NavItem[] = [
{ name: 'Invitar cliente', href: '/admin/invitar-cliente', icon: Send }, { name: 'Invitaciones trial', href: '/admin/invitaciones-trial', icon: Gift },
{ name: 'Configuracion', href: '/configuracion', icon: Settings }, { name: 'Configuracion', href: '/configuracion', icon: Settings },
]; ];