10 KiB
Setup admin global + gestión de clientes (2026-04-26 turno tardío)
Sesión continuación del día. Foco: habilitar al admin global del fork para que pueda operar como plataforma — gestión de clientes, edición de catálogos (planes, add-ons), auto-facturación de pagos, y experiencia de login dedicada.
Relacionado:
2026-04-26-session.md(índice del día),2026-04-26-sprints-1-2-3.md(sprints anteriores), y los docs específicos de IVA y notificaciones.
1. Bootstrap admin global del fork
El fork Horux_despacho no tenía tenant raíz ni platform_admin. Ejecutado:
HORUX_ADMIN_EMAIL=carlos@horuxfin.com \
HORUX_ADMIN_NOMBRE=Carlos \
HORUX_TI_EMAIL=ivan@horuxfin.com \
HORUX_TI_NOMBRE=Ivan \
pnpm bootstrap:admin-global
Crea:
- Tenant
Horux 360(RFCHTS240708LJA, plan enterprise, dbMode MANAGED). carlos@horuxfin.comcon rolplatform_admin(admin global).ivan@horuxfin.comcon rolplatform_ti(TI superset).
Contraseña fijada manualmente a Admin12345! con bcrypt cost 12 +
tokenVersion: increment para invalidar JWTs viejos.
Cuentas finales del fork (todas con Admin12345!):
carlos@horuxfin.com— platform_adminivan@horuxfin.com— platform_tijd@demo.com— owner Patitojf@demo.com— owner Zorrosupervisor@patito.com/auxiliar@patito.com/cliente@patito.com
2. Login y home redirect
apps/web/app/(auth)/login/page.tsx ahora detecta el admin global tras el
login y lo manda directo a /clientes (gestión de tenants), no a /dashboard
(operativo despacho). dashboard/page.tsx también tiene useEffect defensivo
que redirige si llega ahí por URL directa.
const isGlobalAdmin = isGlobalAdminRfc(response.user?.tenantRfc, userRole, platformRoles);
if (isGlobalAdmin) router.push('/clientes');
3. Sidebar — banner "Configuración inicial" oculto
components/layouts/sidebar.tsx: el banner que linkea a /onboarding solo se
muestra cuando (!contribuyentes || contribuyentes.length === 0) && role !== 'cliente' && !isGlobalAdmin.
El admin global nunca tendrá contribuyentes en su tenant raíz Horux 360 — el
banner sería ruido permanente.
(Una versión inicial también limpiaba todos los items operativos del sidebar para el admin global. Revertida — el admin global mantiene acceso completo para impersonar tenants. Solo cambia el banner.)
4. /admin/usuarios — fix roleLabels[role].icon undefined
Bug latente desde que se introdujeron los roles del despacho. roleLabels
solo tenía owner/contador/visor (Horux 360 mainline); cuando aparecía un
user con rol cfo, supervisor, auxiliar o cliente, el lookup retornaba
undefined y roleInfo.icon lanzaba TypeError.
Fix: agregar los 7 roles + defaultRoleInfo como fallback defensivo:
const roleLabels: Record<string, { label: string; icon: typeof Shield; color: string }> = {
owner: { label: 'Dueño', icon: Shield, color: 'text-primary' },
cfo: { label: 'CFO', icon: Briefcase, color: 'text-indigo-600' },
contador: { label: 'Contador', icon: Calculator, color: 'text-green-600' },
supervisor: { label: 'Supervisor', icon: UserCheck, color: 'text-blue-600' },
auxiliar: { label: 'Auxiliar', icon: UserCog, color: 'text-cyan-600' },
cliente: { label: 'Cliente', icon: User, color: 'text-amber-600' },
visor: { label: 'Visor', icon: Eye, color: 'text-muted-foreground' },
};
const defaultRoleInfo = { label: 'Sin rol', icon: User, color: 'text-muted-foreground' };
5. /configuracion/precios-suscripcion — planes despacho
La página leía plan_prices (BD central, planes Horux 360 legacy:
starter/business/business_ia/enterprise). En el fork no aplican.
Reescrita para mostrar los 4 planes despacho desde DESPACHO_PLAN_PRICES:
| Plan | Mensual | Anual 1er año | Anual renovación | RFCs | Timbres/mes |
|---|---|---|---|---|---|
| Mi Empresa | $580 | $5,800 | $5,800 | 1 | 50 |
| Mi Empresa + | $900 | $9,000 | $9,000 | 1 | 50 |
| Business Control | No aplica | $25,850 | $25,850 | 100 | 0 |
| Enterprise | No aplica | $43,000 | $43,000 | 100 | 0 |
Modo read-only porque los precios viven en DESPACHO_PLAN_PRICES
(catálogo estático en @horux/shared).
Capacidad latente para edición
Migración aplicada 20260426230000_despacho_plan_prices:
CREATE TABLE despacho_plan_prices (
plan TEXT PRIMARY KEY,
monthly DECIMAL(10,2),
first_year DECIMAL(10,2) NOT NULL,
renewal DECIMAL(10,2) NOT NULL,
permite_monthly BOOLEAN NOT NULL DEFAULT false,
updated_at TIMESTAMP NOT NULL
);
Seed inicial con los 4 planes y sus valores. Permite migrar a editable cuando se quiera — service + endpoints + UI editable quedan como follow-up.
6. Gestión de clientes (/clientes)
Página enriquecida con KPIs y operaciones para el admin global.
Backend
- Service:
apps/api/src/services/admin-clientes.service.tsgetClientesStats({ from, to })— devuelve:suscripcionesPorPlan: groupBy plan con count (status=authorized).ingresos: sum + count dePayment.amountcon status=approved en rango.noRenovaciones: subs concurrentPeriodEnden rango y status terminal (cancelled / trial_expired / paused) + datos del tenant.usuariosPorCliente: count de memberships activos por tenant.
getTenantUsuarios(tenantId)— drill-down: lista users + rol + isOwner.
- Controller:
controllers/admin-clientes.controller.tsconrequireStaff(req)(platform_admin/ti). - Routes:
routes/admin-clientes.routes.tsmontadas en/api/admin/clientes(GET /stats,GET /:tenantId/usuarios).
UI
- Selector de rango de fechas (default mes en curso).
- 4 KPI cards: clientes registrados / suscripciones activas (con breakdown por plan en pie) / ingresos del periodo / no renovaciones.
- Tabla "Clientes que no renovaron" expandible con detalle (cliente, RFC, plan, vencimiento, status).
- En cada item de la lista de clientes, click sobre el contador de usuarios abre un modal con la lista (email, nombre, rol, badge de owner).
7. Auto-facturación de pagos al cliente
CLAUDE.md ya documentaba invoicing.service.ts:emitInvoiceIfApplicable. Cada
webhook payment.approved lo dispara. Idempotente por Payment.facturapiInvoiceId,
fail-soft si Facturapi falla.
Ajustes en este turno
PLAN_LABELSextendido con los 4 planes despacho. Antes la factura mostraba el codename (mi_empresa,business_cloud) en lugar de "Mi Empresa" / "Enterprise".- Texto "Horux 360" → "Horux Despachos" en la descripción del CFDI y en el concepto de timbres.
Prerequisito operativo (manual del admin)
El emisor de las facturas es el tenant Horux 360 (RFC HTS240708LJA).
Requiere:
- CSD válido subido vía
/configuracion/csdpor Carlos (admin global). tenants.facturapi_org_idpoblado tras crear la organización en Facturapi.
Hoy la columna está en null para el tenant recién bootstrapeado. Mientras
falte, emitInvoiceIfApplicable registra error en consola y Payment.facturapiInvoiceId
queda null. Pendiente operativo: que Carlos suba el CSD.
8. Gestión de add-ons (/configuracion/addons)
Página nueva para que el admin global edite el catálogo de add-ons.
Backend
- Controller:
controllers/admin-addons.controller.tslistCatalogo— incluye count de suscripciones activas por add-on.updateCatalogoItem(id, { nombre?, precio?, active? })con audit log.
- Routes:
/api/admin/addons/catalogo(GET,PUT /:id).requireStaff.
UI
- Tabla con columnas: codename, nombre (editable), precio MXN (editable), frecuencia, suscripciones activas (read-only), estado (badge), acciones.
- Edición inline con dos botones (guardar / cancelar) por fila.
- Toggle de activación con confirmación condicional: si el add-on tiene ≥1 suscripción activa, pide confirmar antes de desactivar.
- Card link agregada al
/configuracionraíz solo visible para admin global.
Add-ons del catálogo actual
lolita_ia_contribuyente— Lolita IA (por contribuyente) — $250/mesmodulo_ia— Módulo IA Fiscal — $390/mesrfcs_extra_10— +10 RFCs adicionales — $190/mesrfcs_extra_50— +50 RFCs adicionales — $690/mestimbres_extra_500— +500 timbres mensuales — $490/mescontribuyente_extra_business_cloud— Contribuyente adicional — $45/mes (overage automático Business Control / Enterprise)
9. Sync al repo OneDrive
Tras cada bloque de cambios, ejecutado:
robocopy "C:\Users\chtr1\Downloads\Horux_despacho" "C:\Users\chtr1\OneDrive\Documentos\GitHub\Horux_despachos" /E `
/XD node_modules .next .turbo dist .pnpm data email-previews .git xmls `
/XF .env .env.local "*.log" tsconfig.tsbuildinfo /NFL /NDL /NJH
Excluye node_modules, builds, caches, secretos, datos de tenants, y el
.git/ del destino para preservar el repo origen.
10. Pendientes derivados
- Cargar CSD del Horux 360 vía
/configuracion/csdpara activar auto-facturación real. Sin esto,emitInvoiceIfApplicablefalla silencioso. - Hacer editable
DespachoPlanPricedesde UI: la migración + seed están aplicados pero falta service + endpoint + UI editable. Mientras tanto la página de precios queda read-only. - Notificación al desactivar add-on con suscripciones activas: hoy se bloquean las contrataciones nuevas pero las activas siguen vigentes. Quizá enviar email al owner avisando que el add-on quedará "deprecated".
- Visual cue del prerequisito faltante de Facturapi org: agregar alerta
en
/clienteso/configuracioncuando el tenant Horux 360 no tengafacturapi_org_idpara que Carlos lo configure.
11. Pendientes históricos (sin cambio)
- SMTP
.envprod (cron de notificaciones loguea a consola sin SMTP). - Cloudflare Tunnel +
FRONTEND_URLHTTPS dev. - Typecheck web cleanup (~18 errores preexistentes).
- Nómina (tipo N) y Carta Porte sin implementar.
- SAT rejections — esperando captura real.
- Re-notificación bug 2.4 visibilidad auxiliares (sin reproducción específica).