feat: facturación primer pago, fixes SAT/MP, autocompletado RFCs/conceptos
Backend: - Notificación email al admin cuando llega primer pago aprobado (sin factura auto) - Endpoints GET /pagos-sin-factura y POST /emitir-factura-pago para admin global - Fix vinculación org Facturapi Horux 360 (69f23a5a242e0af47a41fa0d) - Fix webhook MP: validación defensiva de x-signature header - Fix autocompleto RFCs: eliminado filtro por contribuyenteId - Fix autocompleto conceptos: eliminado filtro por contribuyenteId - SAT fixes: anti-bot CSF scraper, request reuse, date range fix, stale job thresholds - SAT sync request reuse across jobs para evitar agotar cuota diaria - Typo fix MP_ACCESS_TOKEN en .env - Trial invitations system backend Frontend: - Nueva página /admin/facturas-pendientes con tabla y emisión manual - Métrica 'Facturas pendientes' en /clientes (clickable) - Navegación onboarding FIEL/CSD corregida - Sidebar themes sincronizados - Fix SAT portal migration scraper (NetIQ) - Trial invitation acceptance pages
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
-- DropIndex
|
||||
DROP INDEX "subscription_addons_subscription_id_plan_addon_catalogo_id_key";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "trial_invitations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"invited_by" TEXT NOT NULL,
|
||||
"plan" TEXT NOT NULL DEFAULT 'business_control',
|
||||
"duration_days" INTEGER NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||
"token" TEXT NOT NULL,
|
||||
"email_sent_to" TEXT,
|
||||
"sent_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"accepted_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "trial_invitations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "trial_invitations_token_key" ON "trial_invitations"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "trial_invitations_tenant_id_idx" ON "trial_invitations"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "trial_invitations_token_idx" ON "trial_invitations"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "trial_invitations_status_idx" ON "trial_invitations"("status");
|
||||
@@ -477,6 +477,29 @@ model TrialUsage {
|
||||
@@map("trial_usages")
|
||||
}
|
||||
|
||||
/// Invitaciones de trial enviadas por admin global a tenants específicos.
|
||||
/// Permite activar trials configurables (ej. Business Control Prueba por 60 días)
|
||||
/// con un link único que el owner del tenant puede aceptar.
|
||||
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") // pending | accepted | expired | cancelled
|
||||
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")
|
||||
|
||||
@@index([tenantId])
|
||||
@@index([token])
|
||||
@@index([status])
|
||||
@@map("trial_invitations")
|
||||
}
|
||||
|
||||
/// Catálogo despacho — precios + limits editables por admin global.
|
||||
/// Las `features` siguen viviendo en TS (`DESPACHO_PLANS` en `@horux/shared`)
|
||||
/// porque están acopladas a UI/middleware y son contrato de código.
|
||||
|
||||
@@ -162,8 +162,8 @@ async function main() {
|
||||
{ plan: 'custom', nombre: 'Custom', monthly: null, firstYear: null, renewal: null, permiteMonthly: false, maxRfcs: 1, maxUsers: 3, timbresIncluidosMes: 50, dbMode: 'MANAGED' as const, permiteServidorBackup: false, permiteSatIncremental: false },
|
||||
{ plan: 'mi_empresa', nombre: 'Mi Empresa', monthly: 580, firstYear: 5800, renewal: 5800, permiteMonthly: true, maxRfcs: 1, maxUsers: 3, timbresIncluidosMes: 50, dbMode: 'MANAGED' as const, permiteServidorBackup: false, permiteSatIncremental: false },
|
||||
{ plan: 'mi_empresa_plus', nombre: 'Mi Empresa +', monthly: 900, firstYear: 9000, renewal: 9000, permiteMonthly: true, maxRfcs: 1, maxUsers: 3, timbresIncluidosMes: 50, dbMode: 'MANAGED' as const, permiteServidorBackup: false, permiteSatIncremental: true },
|
||||
{ plan: 'business_control', nombre: 'Business Control', monthly: null, firstYear: 25850, renewal: 25850, permiteMonthly: false, maxRfcs: 100, maxUsers: -1, timbresIncluidosMes: 0, dbMode: 'BYO' as const, permiteServidorBackup: true, permiteSatIncremental: true },
|
||||
{ plan: 'business_cloud', nombre: 'Enterprise', monthly: null, firstYear: 43000, renewal: 43000, permiteMonthly: false, maxRfcs: 100, maxUsers: -1, timbresIncluidosMes: 0, dbMode: 'BYO' as const, permiteServidorBackup: true, permiteSatIncremental: true },
|
||||
{ plan: 'business_control', nombre: 'Business Control', monthly: null, firstYear: 25850, renewal: 25850, permiteMonthly: false, maxRfcs: 100, maxUsers: -1, timbresIncluidosMes: 0, dbMode: 'MANAGED' as const, permiteServidorBackup: true, permiteSatIncremental: true },
|
||||
{ plan: 'business_cloud', nombre: 'Enterprise', monthly: null, firstYear: 43000, renewal: 43000, permiteMonthly: false, maxRfcs: 100, maxUsers: -1, timbresIncluidosMes: 0, dbMode: 'MANAGED' as const, permiteServidorBackup: true, permiteSatIncremental: true },
|
||||
];
|
||||
for (const p of DESPACHO_PLAN_CATALOGO) {
|
||||
await prisma.despachoPlanPrice.upsert({
|
||||
|
||||
Reference in New Issue
Block a user