Factura Global & fecha_efectiva: - Migracion 045_factura_global.sql: periodicidad, meses_global, año_global, fecha_efectiva - sat-parser.service.ts: extrae InformacionGlobal del XML - sat.service.ts: calcFechaEfectiva con soporte bimestral (periodicidad 05) - metricas-compute, dashboard, impuestos, cfdi, export, conciliacion, alertas: reemplaza fecha_emision-1h por COALESCE(fecha_efectiva, fecha_emision-1h) - Script recalc-metricas.ts para recalculo manual Fallback datos fiscales tenant → contribuyente: - contribuyente.service.ts: fetchTenantFiscalData + mergeContribuyenteWithTenant rellena regimenFiscal, codigoPostal y domicilio cuando el contribuyente tiene el mismo RFC que el tenant y sus campos estan vacios - contribuyente.controller.ts y contribuyente-config.controller.ts: pasan req.user!.tenantId al servicio Fix critico SAT sync: - sat.service.ts: anio_global → año_global en INSERT/UPDATE de CFDIs (la migracion creo 'año_global' con tilde; el codigo usaba 'anio_global', causando fallo en 100% de inserciones de CFDI) - determineChunkMonths: salta sondeo si existe job previo con requestIds - MAX_POLL_ATTEMPTS: 45 → 500 (~8h) para syncs iniciales grandes Docs: - docs/sessions/2026-05-22-factura-global-contribuyente-fallback.md
237 lines
11 KiB
Markdown
237 lines
11 KiB
Markdown
# Sesión: Trial Business Control Prueba (Invitación desde Admin Global)
|
|
|
|
**Fecha:** 2026-05-04
|
|
**Feature:** Sistema de invitaciones de trial configurable para plan Business Control
|
|
|
|
---
|
|
|
|
## 1. Requerimiento
|
|
|
|
Crear un trial específico para el plan **Business Control**, llamado "Business Control Prueba", con las siguientes características:
|
|
|
|
1. **Registro simplificado**: Los nuevos usuarios se registran sin escoger plan. Todos empiezan con el trial genérico actual (30 días, 3 RFCs, 1 usuario, MANAGED).
|
|
2. **Invitación desde admin global**: El administrador global puede enviar una invitación a un tenant para activar "Business Control Prueba".
|
|
3. **Periodo gratuito configurable**: El admin define cuántos días dura la prueba (1-365 días).
|
|
4. **Todas las features de Business Control**: Durante el trial, el tenant tiene 100 RFCs, usuarios ilimitados, API, SAT incremental, etc.
|
|
5. **Al vencer**: El tenant debe pagar la suscripción de Business Control para continuar.
|
|
|
|
---
|
|
|
|
## 2. Decisiones Arquitectónicas
|
|
|
|
### 2.1 No nuevo enum Plan
|
|
No se agregó `business_control_trial` al enum `Plan`. En su lugar:
|
|
- `tenant.plan` se actualiza a `business_control` al aceptar la invitación.
|
|
- `subscription.plan` = `business_control`, `subscription.status` = `'trial'`.
|
|
- `tenant.trialEndsAt` se recalcula según los días configurados.
|
|
|
|
**Ventaja**: El feature-gate (`requireFeature`) y los límites (`DESPACHO_PLANS['business_control']`) funcionan automáticamente.
|
|
|
|
### 2.2 dbMode siempre MANAGED (Opción 1)
|
|
**Decisión arquitectónica clave**: Todos los tenants, sin importar el plan, siempre tienen `dbMode: 'MANAGED'`. El conector/BYO es una **feature de respaldo** que se activa por separado sin cambiar el modo de la base de datos.
|
|
|
|
**Razón**: El BYO es "como respaldo" para cuando fallen los servicios en la nube, pero muchos clientes no tendrán servidor físico desde el inicio. Business Control y Enterprise operan 100% en la nube; el conector local es un respaldo opcional.
|
|
|
|
**Cambios aplicados**:
|
|
- `signupDespacho()`: siempre crea tenant con `dbMode: 'MANAGED'`
|
|
- `provisionConnector()`: ya NO cambia `dbMode` a `'BYO'`; solo guarda `connectorTokenEnc` y `connectorTunnelHostname`
|
|
- `DESPACHO_PLANS`: `business_control` y `business_cloud` ahora tienen `dbMode: 'MANAGED'`
|
|
- Seed de `despacho_plan_prices`: actualizado a `MANAGED` para esos planes
|
|
- BD: `UPDATE despacho_plan_prices SET db_mode = 'MANAGED' WHERE plan IN ('business_control', 'business_cloud')`
|
|
|
|
### 2.3 Registro simplificado
|
|
Se eliminó la selección de plan y frecuencia del registro de despacho (`/register-despacho`). Todos los nuevos usuarios empiezan con trial genérico de 30 días.
|
|
|
|
---
|
|
|
|
## 3. Cambios Implementados
|
|
|
|
### 3.1 Backend
|
|
|
|
#### Migración Prisma — `TrialInvitation`
|
|
Nueva tabla en `apps/api/prisma/schema.prisma`:
|
|
```prisma
|
|
model TrialInvitation {
|
|
id String @id @default(uuid())
|
|
tenantId String @map("tenant_id")
|
|
invitedBy String @map("invited_by")
|
|
plan String @default("business_control")
|
|
durationDays Int @map("duration_days")
|
|
status String @default("pending")
|
|
token String @unique
|
|
emailSentTo String? @map("email_sent_to")
|
|
sentAt DateTime @default(now()) @map("sent_at")
|
|
expiresAt DateTime @map("expires_at")
|
|
acceptedAt DateTime? @map("accepted_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
}
|
|
```
|
|
|
|
**Migración aplicada:** `20260507201624_add_trial_invitations`
|
|
|
|
#### Nuevo Service: `apps/api/src/services/trial-invitations.service.ts`
|
|
- `createInvitation()` — Crea invitación, genera token, envía email al owner
|
|
- `acceptInvitation()` — Valida token, actualiza tenant.plan, crea subscription trial
|
|
- `getInvitations()` — Lista invitaciones con enrich de tenant data
|
|
- `getPendingInvitationForTenant()` — Obtiene invitación pendiente para un tenant
|
|
- `cancelInvitation()` — Cancela invitación pendiente
|
|
|
|
#### Nuevo Controller: `apps/api/src/controllers/trial-invitations.controller.ts`
|
|
Endpoints:
|
|
- `POST /api/invitations/trial` — Solo global admin. Body: `{ tenantId, plan?, durationDays }`
|
|
- `GET /api/invitations/trial` — Solo global admin. Lista todas.
|
|
- `GET /api/invitations/trial/pending` — Autenticado. Devuelve invitación pendiente para el tenant.
|
|
- `POST /api/invitations/trial/:token/accept` — Autenticado. Owner del tenant.
|
|
- `POST /api/invitations/trial/:id/cancel` — Solo global admin.
|
|
- `GET /api/invitations/trial/token/:token` — Público (autenticado). Detalles de invitación.
|
|
|
|
#### Nuevas Routes: `apps/api/src/routes/trial-invitations.routes.ts`
|
|
Registradas en `app.ts` bajo `/api/invitations/trial`.
|
|
|
|
#### Modificación: `apps/api/src/controllers/despacho.controller.ts`
|
|
`getMyPlan()` ahora respeta `subscription.plan` cuando `status === 'trial'`:
|
|
```ts
|
|
if (subscription?.status === 'trial' && subscription.plan && subscription.plan !== 'trial') {
|
|
currentPlan = subscription.plan; // 'business_control' si es Business Control Prueba
|
|
} else if (isTrialActive) {
|
|
currentPlan = 'trial';
|
|
} else {
|
|
currentPlan = String(tenant.plan);
|
|
}
|
|
```
|
|
|
|
Schema Zod de signup: `plan` y `frequency` ahora son puramente opcionales (sin defaults forzados).
|
|
|
|
#### Modificación: `apps/api/src/services/despacho.service.ts`
|
|
- `signupDespacho()` ya no crea suscripción pagada durante el registro.
|
|
- Todos los nuevos tenants empiezan con `plan: 'trial'`, `dbMode: 'MANAGED'`.
|
|
- No se devuelve `paymentUrl` en el response.
|
|
|
|
#### Nuevo template de email: `apps/api/src/services/email/templates/trial-invitation.ts`
|
|
Email de invitación con link de activación y detalles del plan.
|
|
|
|
#### Modificación: `apps/api/src/services/email/email.service.ts`
|
|
Agregado método `sendTrialInvitation()`.
|
|
|
|
### 3.2 Frontend
|
|
|
|
#### Registro simplificado: `apps/web/app/(auth)/register-despacho/page.tsx`
|
|
- Eliminado step 3 (selección de plan y frecuencia).
|
|
- Solo 2 pasos: datos del despacho/owner → selección de vertical.
|
|
- No se envía `plan` ni `frequency` en el POST.
|
|
- Redirige a `/onboarding` tras registro exitoso.
|
|
|
|
#### Nueva página: `apps/web/app/invitacion/trial/[token]/page.tsx`
|
|
- Muestra detalles de la invitación (plan, días, despacho).
|
|
- Botón "Aceptar invitación" (requiere autenticación como owner).
|
|
- Si no está logueado, redirige a login con redirect.
|
|
- Estados: loading, inválida, expirada, aceptada, éxito.
|
|
|
|
#### Modificación: `apps/web/app/(dashboard)/configuracion/planes-despacho/page.tsx`
|
|
- Detecta invitación pendiente vía `GET /api/invitations/trial/pending`.
|
|
- Muestra banner destacado: "Invitación especial — Business Control Prueba" con botón "Activar ahora".
|
|
- Al aceptar, recarga el plan info y muestra mensaje de éxito.
|
|
|
|
#### Nueva página de admin: `apps/web/app/(dashboard)/admin/invitaciones-trial/page.tsx`
|
|
- Formulario para crear invitación: selector de despacho, plan, duración en días.
|
|
- Tabla de historial con estados (Pendiente, Aceptada, Expirada, Cancelada).
|
|
- Acción cancelar para invitaciones pendientes.
|
|
|
|
#### Modificación: `apps/web/components/layouts/sidebar.tsx`
|
|
Agregado link "Invitaciones Trial" a la navegación de admin global.
|
|
|
|
#### Nuevo API client: `apps/web/lib/api/trial-invitations.ts`
|
|
Helpers para consumir todos los endpoints de invitaciones.
|
|
|
|
---
|
|
|
|
## 4. Flujo de Uso
|
|
|
|
### 4.1 Admin envía invitación
|
|
1. Admin global navega a **Invitaciones Trial** en el sidebar.
|
|
2. Selecciona un despacho, elige plan (Business Control o Enterprise), define duración (ej. 60 días).
|
|
3. Clic en "Enviar invitación".
|
|
4. El backend crea el token y envía email al owner del despacho.
|
|
|
|
### 4.2 Owner recibe y acepta
|
|
1. Owner recibe email con link `/invitacion/trial/{token}`.
|
|
2. Abre el link (si no está logueado, va a login primero).
|
|
3. Ve los detalles de la invitación y clic en "Aceptar invitación".
|
|
4. El backend:
|
|
- Actualiza `tenant.plan = 'business_control'`
|
|
- Actualiza `tenant.trialEndsAt = now + 60 días`
|
|
- Marca subscriptions trial anteriores como `trial_converted`
|
|
- Crea nueva subscription con `plan: 'business_control', status: 'trial'`
|
|
- Marca invitación como `accepted`
|
|
5. Owner es redirigido a `/configuracion/planes-despacho`.
|
|
|
|
### 4.3 Durante el trial
|
|
- El tenant tiene todas las features de Business Control (100 RFCs, usuarios ilimitados, API, etc.).
|
|
- `getMyPlan()` retorna `plan: 'business_control'` en lugar de `'trial'`.
|
|
- Feature-gate permite acceso a todas las funciones del plan.
|
|
|
|
### 4.4 Al vencer
|
|
- `plan-limits.middleware` detecta `needsRenewal = true`.
|
|
- Bloquea escrituras (POST/PUT/DELETE) con código `SUBSCRIPTION_INACTIVE`.
|
|
- Muestra banner: "Tu prueba gratuita terminó. Renueva tu plan para continuar."
|
|
- Owner puede contratar Business Control desde `/configuracion/planes-despacho`.
|
|
|
|
---
|
|
|
|
## 5. Archivos Modificados/Creados
|
|
|
|
### Backend (nuevos)
|
|
- `apps/api/src/services/trial-invitations.service.ts`
|
|
- `apps/api/src/controllers/trial-invitations.controller.ts`
|
|
- `apps/api/src/routes/trial-invitations.routes.ts`
|
|
- `apps/api/src/services/email/templates/trial-invitation.ts`
|
|
- `apps/api/prisma/migrations/20260507201624_add_trial_invitations/migration.sql`
|
|
|
|
### Backend (modificados)
|
|
- `apps/api/prisma/schema.prisma`
|
|
- `apps/api/prisma/seed.ts`
|
|
- `apps/api/src/app.ts`
|
|
- `apps/api/src/services/despacho.service.ts`
|
|
- `apps/api/src/controllers/despacho.controller.ts`
|
|
- `apps/api/src/services/email/email.service.ts`
|
|
- `apps/api/src/services/connector.service.ts`
|
|
- `apps/api/src/services/admin-dashboard.service.ts`
|
|
- `packages/shared/src/constants/despacho-plans.ts`
|
|
|
|
### Frontend (nuevos)
|
|
- `apps/web/app/invitacion/trial/[token]/page.tsx`
|
|
- `apps/web/app/(dashboard)/admin/invitaciones-trial/page.tsx`
|
|
- `apps/web/lib/api/trial-invitations.ts`
|
|
|
|
### Frontend (modificados)
|
|
- `apps/web/app/(auth)/register-despacho/page.tsx`
|
|
- `apps/web/app/(dashboard)/configuracion/planes-despacho/page.tsx`
|
|
- `apps/web/components/layouts/sidebar.tsx`
|
|
|
|
---
|
|
|
|
## 6. Deploy
|
|
|
|
```bash
|
|
cd /root/HoruxDespachosNuevo
|
|
# Backend: migración ya aplicada + build implícito en reload
|
|
# Frontend:
|
|
pnpm build --filter=@horux/web
|
|
# PM2 reload:
|
|
pm2 reload horux-api
|
|
pm2 reload horux-web
|
|
```
|
|
|
|
**Estado:** ✅ Exitoso. Build sin errores. Procesos reiniciados.
|
|
|
|
---
|
|
|
|
## 7. Notas para Futuras Sesiones
|
|
|
|
- Si se quiere agregar más planes a las invitaciones (ej. `mi_empresa_plus`), solo hay que agregarlos al Select del frontend de admin.
|
|
- El email de invitación usa `FRONTEND_URL` del environment. Si no está seteado, fallback a `https://app.horux360.com`.
|
|
- La invitación expira en 7 días desde el envío (configurable en `createInvitation()`).
|
|
- Si un tenant en trial genérico acepta Business Control Prueba, su trial anterior se pierde (se sobreescribe `trialEndsAt`). Esto es intencional.
|
|
- Considerar agregar un cron que marque invitaciones expiradas automáticamente (hoy solo se marcan al intentar aceptar).
|
|
- **dbMode siempre MANAGED**: El conector/BYO es una feature de respaldo independiente. Nunca cambiar `dbMode` a `'BYO'` en ningún flujo. Si en el futuro se quiere usar la conexión BYO como fallback cuando la nube falla, implementarlo en el `tenant.middleware.ts` como lógica de retry/fallback, no como modo principal.
|