Skip to content

Admin fees

SipSop factura fees regulatorios, passthroughs gubernamentales e impuestos de ventas como line items separados en cada factura. Esta página documenta el schema del catálogo de fees, cómo crear y actualizar fees, carrier modes, y cómo los fees fluyen hacia las facturas de Stripe y el componente InvoiceBreakdown.

Fase 16h

Este sistema fue implementado en la Fase 16h del roadmap. La tabla pública de fees se sirve en GET /api/v1/fees/public y se renderiza en /fees en el landing site. El componente de desglose de factura (InvoiceBreakdown) lee los line items desde trpc.billing.invoiceLineItems.

Schema de base de datos

Cuatro tablas en packages/db/src/schema/fees.ts:

fee_definitions

El catálogo maestro de todos los fees. Columnas clave:

ColumnaTipoDescripción
iduuidPK
codetextIdentificador único, ej: REGULATORY_RECOVERY, SALES_TAX_NJ. Solo MAYÚSCULAS, letras/números/guión bajo.
nametextNombre visible para clientes
typetextUno de: percentage, flat_per_line, flat_per_invoice, percentage_of_interstate
ratenumeric(10,8)Valor de la tasa. Para tipos porcentaje: 0.09 = 9%. Para tipos flat: monto en USD.
jurisdictiontextFEDERAL, NJ, NY, NYC, CT, o ALL
categorytextprovider_fee, government_passthrough, o tax
taxablebooleanSi este fee es parte de la base imponible de sales tax. Provider fees = true; los impuestos mismos = false.
compliance_statustextactive, inactive, o configured_pending_registration
requires_registrationbooleanSi se requiere registro regulatorio (FCC 499, NJ BPU, etc.) antes de activar
registration_authoritytextFCC, NJ_BPU, NJ_DOT, NY_DTF, etc.
registration_idtextEl ID de cert/filer real una vez aprobado
registration_statustextnot_started, in_progress, pending_approval, approved
effective_fromdateCuándo empezó a aplicarse esta versión del fee
effective_todateCuándo dejó de aplicarse esta versión (NULL = vigente)
supersedes_iduuidApunta a la versión anterior cuando un cambio de rate crea una nueva fila

Regla de negocio: Nunca hacer hard-delete de fee definitions. Soft-delete via compliance_status='inactive' + effective_to=hoy. Los cambios de rate crean una nueva versión (cierra la fila anterior, inserta una nueva con supersedes_id). Esto preserva el snapshot histórico para cada invoice_line_items.fee_definition_id.

fee_definition_history

Audit log inmutable llenado por un trigger de Postgres en cada INSERT/UPDATE/DELETE a fee_definitions. Nunca escribir en esta tabla desde código de aplicación.

ColumnaDescripción
operationINSERT, UPDATE, o DELETE
previous_valuesSnapshot JSONB antes del cambio (NULL para INSERT)
new_valuesSnapshot JSONB después del cambio (NULL para DELETE)
changed_by_user_idSeteado via variable de sesión app.current_user_id
change_reasonSeteado via variable de sesión app.change_reason

tenant_billing_config

Un row por tenant. Almacena dirección fiscal, código de jurisdicción, carrier mode override, estado de exención impositiva, y exclusiones de fees por cliente.

ColumnaDescripción
jurisdiction_codeDerivado de billing address: NJ, NY-NYC, NY-WESTCHESTER, etc.
carrier_modereseller_retail, reseller_wholesale, o clec_direct — puede sobrescribir el default del sistema
tax_exemptSi es true, no se agregan line items de sales tax
tax_exempt_certificateNúmero de certificado para clientes exentos
excluded_fee_codesArray de fee_definitions.code a omitir para este tenant. Para descuentos negociados. NO afecta sales tax (usar tax_exempt para eso).

invoice_line_items

Un row por línea en una factura generada. Referencia al fee_definition_id que estaba activo en ese momento, preservando las tasas históricas.

ColumnaDescripción
typeservice, addon, usage, fee, o tax
codeEl código del fee (o código del plan para service/addon)
unit_amount_centsCents enteros, nunca floats
amount_centsTotal de la línea: unit_amount_cents × quantity
fee_definition_idFK a fee_definitions. NULL para tipos service, addon, usage.
jurisdictionJurisdicción que disparó esta línea

billing_system_config

Fila singleton (id = 'singleton'). Tiene el carrier mode global por defecto y los IDs de certs regulatorios (FCC 499 Filer ID, NJ BPU CLEC cert, NJ DoT cert, NY DTF cert).

Tipos de fee y cálculo

percentage          → monto = subtotalCents × rate  (ej: rate=0.09625 = 9.625%)
flat_per_line       → monto = rate × 100 × activeLines  (rate en USD → cents)
flat_per_invoice    → monto = rate × 100  (monto fijo por factura)
percentage_of_interstate → monto = interstateUsageCents × rate

Todos los montos se almacenan y calculan en cents enteros. Sin floats en ningún punto del pipeline de facturación.

Carrier modes

El carrier mode determina qué fees es responsable de cobrar y remitir SipSop:

ModoDescripciónQuién maneja passthroughs gubernamentales
reseller_retailSipSop revende Twilio al retail. Twilio remite E911, FUSF, Cost Recovery. SipSop solo agrega provider fees + sales tax.Twilio
reseller_wholesaleSipSop carrier registrado, Twilio wholesale. SipSop cobra y remite TODOS los fees activos.SipSop
clec_directSipSop como CLEC directo. Máximo control, máxima carga de compliance.SipSop

Cambiar carrier mode es una acción exclusiva de super_admin y requiere:

  • Para reseller_wholesale: FCC 499 Filer ID configurado + todos los fees de government passthrough aprobados
  • Para clec_direct: FCC 499 Filer ID + Certificación NJ BPU CLEC

La API verifica estos requisitos y bloquea el cambio si no se cumplen. Se requiere un changeReason y la confirmación exacta "I understand this changes regulatory liability".

Procedimientos tRPC (router admin-fees)

Acceso: billingClerkProcedure para CRUD; superAdminProcedure para cambios de carrier mode.

ProcedimientoTipoDescripción
adminFees.listqueryLista fees con filtros opcionales: jurisdiction, category, complianceStatus, search
adminFees.getqueryObtiene un fee por ID
adminFees.historyqueryHistorial paginado de fee_definition_history
adminFees.createmutationCrea nueva definición de fee
adminFees.updatemutationActualiza fee. Si el rate cambia, cierra la versión anterior y crea una nueva.
adminFees.deactivatemutationSoft-delete: setea compliance_status='inactive', effective_to=hoy
adminFees.testCalculationqueryPreview de lo que cobraría un fee dado subtotal/líneas/uso — sin writes en DB
adminFees.impactAnalysisqueryCuántas facturas/tenants usaron este fee en los últimos 90 días
adminFees.compliancequeryDashboard de compliance: totales por estado + tracker de registro por authority
adminFees.quarterlyReportquerySuma de cada fee cobrado en un trimestre (formato: Q1_2026) para filing de remittance
adminFees.updateRegistrationmutationActualiza estado de registro para todos los fees de una authority. También actualiza IDs de cert en billing_system_config.
adminFees.getCarrierModequeryModo actual + checklist de requisitos para upgrade a wholesale/CLEC
adminFees.setCarrierModemutationCambiar carrier mode global. Solo super_admin.

Crear un fee nuevo

  1. Ir a /admin/billing/fees/new en el portal.
  2. Campos obligatorios: code (MAYÚSCULAS, ej: E911_CT), name, type, rate, jurisdiction, category, taxable.
  3. Elegir compliance_status:
    • inactive — el fee existe en el catálogo pero no aparecerá en facturas.
    • configured_pending_registration — el fee está configurado pero esperando aprobación regulatoria.
    • active — el fee se agregará a las nuevas facturas. Si requires_registration=true, debe tener registration_status='approved' primero (la API lo verifica).
  4. Proveer un changeReason (mínimo 10 caracteres) — se almacena en fee_definition_history.

Ejemplo: NJ 911 surcharge

code: E911_NJ
name: NJ 911 Emergency Response Surcharge
type: flat_per_line
rate: 0.90          ← $0.90 por línea activa por mes
jurisdiction: NJ
category: government_passthrough
taxable: false
requires_registration: true
registration_authority: NJ_DOT

Actualizar un rate

Cuando cambia una tasa gubernamental (ej: el porcentaje de FUSF se actualiza trimestralmente):

  1. Ir a /admin/billing/fees/{id} en el portal.
  2. Actualizar el campo rate y proveer changeReason.
  3. El servicio cierra la versión actual (effective_to=hoy) y crea una nueva fila (supersedes_id apunta al ID anterior). El code queda en la nueva fila.
  4. Los invoice_line_items.fee_definition_id existentes siguen apuntando a la versión anterior — su tasa histórica se preserva.

Versionado de rate change

No actualizar rate manualmente para períodos pasados. Siempre pasar por la UI/API que crea una nueva versión. Saltear esto rompe la cadena de snapshots históricos usada para reporting de compliance.

Endpoint público de fees

GET /api/v1/fees/public (en apps/api/src/routes/fees-public.ts) devuelve todos los fees active con caché de 5 minutos. Expone solo: code, name, type, rate, jurisdiction, category, taxable. Las notas internas, IDs de registro y metadata nunca se exponen.

Este endpoint alimenta la página pública /fees en el landing site.

Componente de desglose de factura

apps/portal/src/components/billing/invoice-breakdown.tsx renderiza el desglose de line items por factura. Hace:

  1. Llama a trpc.billing.invoiceLineItems.useQuery({ invoiceId }).
  2. Agrupa items por tipo: servicios/addons/uso primero, luego fees, luego impuestos.
  3. Muestra un subtotal después de servicios, un subtotal antes de impuestos, y un total general.
  4. Renderiza un tooltip (icono Info de lucide-react) para códigos de fee conocidos usando el mapa FEE_INFO hard-codeado en el componente.

Códigos de fee con tooltips: REGULATORY_RECOVERY, ADMIN_FEE, SALES_TAX_NJ, SALES_TAX_NY_NYC, SALES_TAX_NY_STATE, E911_NJ, FUSF_FEDERAL.

Dashboard de compliance

trpc.adminFees.compliance devuelve:

  • systemConfigbilling_system_config actual con IDs de cert
  • authorities — agrupado por registration_authority, con conteos de aprobados/pendientes/no iniciados
  • totals — conteos total/activos/inactivos/pendingRegistration de fees

Usar esto para verificar que todos los registros requeridos estén en orden antes de cambiar el carrier mode.

Reporte trimestral de compliance

Ejecutar trpc.adminFees.quarterlyReport({ period: 'Q1_2026' }) para obtener el monto total cobrado por cada código de fee durante el trimestre. Usar para:

  • Filing trimestral de NJ Sales Tax (formulario ST-50) — suma de items SALES_TAX_NJ
  • Remittance de FUSF (si se está en modo wholesale)
  • Remittance del surcharge E911 al estado

El reporte agrupa por código de fee e incluye invoiceCount, tenantCount y totalAmountCents.

Audit log

Cada operación de escritura (crear, actualizar, desactivar, cambio de carrier mode, actualización de registro) escribe en audit_log. La tabla es append-only — sin UPDATEs ni DELETEs.

Acciones registradas:

actionDisparador
fee.createNuevo fee creado
fee.updateActualización sin cambio de rate
fee.rate_changeRate cambiado (nueva versión creada)
fee.deactivateFee soft-deleted
carrier_mode.changeCarrier mode global cambiado
registration.updateEstado de registro actualizado para una authority

Para consultar eventos de auditoría recientes:

sql
SELECT timestamp, action, entity_id, payload, ip_address
FROM audit_log
WHERE entity_type = 'fee_definition'
ORDER BY timestamp DESC
LIMIT 50;

Documentación de SipSop. Producto operado por Sopinf Tech LLC.