feat(umind): mide el consumo de IA, OCR y transcripción por tenant

Es la base del cobro por uso: hasta ahora no había ninguna medición de
consumo en todo el repo.

- UmindUso registra cada evento facturable con el costo YA calculado al
  precio vigente del plan. Congelarlo evita que subir un precio revalúe
  consumo pasado, que haría indefendible una factura ante un reclamo.
- callAI devuelve los tokens que reportó el proveedor (campo usage, igual
  en todos los OpenAI-compatibles; input+output en Anthropic). Se mide
  cada ronda de tool-calling, no solo la última: todas gastan tokens.
- ExtraerTextoOCR y TranscribirAudioSelfHosted reciben agenteID; 0 = no
  medir, que es lo que pasan los botones "Probar" del panel de staff.
- Aviso al superar el tope del plan, una vez por mes y sin cortar el
  servicio. El flag de "ya avisé" es en memoria a propósito.
- GET /app/umind/uso con filtros de fecha: resumen por tipo + detalle.
- Test del cálculo de costo por tipo, incluida fracción de 1k tokens y
  tenant sin plan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-13 11:41:23 -05:00
co-authored by Claude Sonnet 5
parent 96cd24f17d
commit 08265510ea
29 changed files with 516 additions and 135 deletions
+42 -42
View File
@@ -134,18 +134,18 @@ type CFZoneAccount struct {
// CFZone representa una zona (dominio) en Cloudflare.
type CFZone struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Paused bool `json:"paused"`
Type string `json:"type"`
NameServers []string `json:"name_servers"`
OriginalNS []string `json:"original_name_servers"`
CreatedOn string `json:"created_on"`
ModifiedOn string `json:"modified_on"`
ActivatedOn string `json:"activated_on"`
Account CFZoneAccount `json:"account"`
Plan CFPlan `json:"plan"`
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Paused bool `json:"paused"`
Type string `json:"type"`
NameServers []string `json:"name_servers"`
OriginalNS []string `json:"original_name_servers"`
CreatedOn string `json:"created_on"`
ModifiedOn string `json:"modified_on"`
ActivatedOn string `json:"activated_on"`
Account CFZoneAccount `json:"account"`
Plan CFPlan `json:"plan"`
}
// CFPlan representa el plan de una zona.
@@ -162,30 +162,30 @@ type CFDNSRecordSettings struct {
// CFDNSRecord representa un registro DNS.
type CFDNSRecord struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
Comment string `json:"comment,omitempty"`
Tags []string `json:"tags,omitempty"`
Settings CFDNSRecordSettings `json:"settings,omitempty"`
PrivateRouting bool `json:"private_routing,omitempty"`
Proxied bool `json:"proxied"`
Proxiable bool `json:"proxiable"`
TTL int `json:"ttl"`
Priority int `json:"priority,omitempty"`
CreatedOn string `json:"created_on"`
ModifiedOn string `json:"modified_on"`
CommentModifiedOn string `json:"comment_modified_on,omitempty"`
TagsModifiedOn string `json:"tags_modified_on,omitempty"`
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
Comment string `json:"comment,omitempty"`
Tags []string `json:"tags,omitempty"`
Settings CFDNSRecordSettings `json:"settings,omitempty"`
PrivateRouting bool `json:"private_routing,omitempty"`
Proxied bool `json:"proxied"`
Proxiable bool `json:"proxiable"`
TTL int `json:"ttl"`
Priority int `json:"priority,omitempty"`
CreatedOn string `json:"created_on"`
ModifiedOn string `json:"modified_on"`
CommentModifiedOn string `json:"comment_modified_on,omitempty"`
TagsModifiedOn string `json:"tags_modified_on,omitempty"`
}
// CFSSLStatus representa un certificate pack de una zona.
type CFSSLStatus struct {
ID string `json:"id"`
Type string `json:"type"` // universal, advanced, custom, sni_custom
Type string `json:"type"` // universal, advanced, custom, sni_custom
Hosts []string `json:"hosts"`
Status string `json:"status"` // active, pending_validation, deleted
Status string `json:"status"` // active, pending_validation, deleted
ValidationMethod string `json:"validation_method,omitempty"`
ValidityDays int `json:"validity_days,omitempty"`
CertificateAuthority string `json:"certificate_authority,omitempty"`
@@ -243,10 +243,10 @@ type CFTokenVerify struct {
// CFTokenPolicy representa una política de permisos del token.
type CFTokenPolicy struct {
ID string `json:"id"`
Effect string `json:"effect"`
Resources map[string]string `json:"resources"`
PermGroups []CFTokenPermGroup `json:"permission_groups"`
ID string `json:"id"`
Effect string `json:"effect"`
Resources map[string]string `json:"resources"`
PermGroups []CFTokenPermGroup `json:"permission_groups"`
}
// CFTokenPermGroup un grupo de permisos.
@@ -478,14 +478,14 @@ func (c *CloudflareClient) doRequest(method, path string, payload interface{}, d
// CFDNSRecordInput es el payload para crear o actualizar un registro DNS.
type CFDNSRecordInput struct {
Type string `json:"type"` // A, AAAA, CNAME, TXT, MX, NS, SRV, CAA…
Name string `json:"name"` // Nombre del registro (ej. "www" o "@")
Content string `json:"content"` // Valor del registro
TTL int `json:"ttl"` // 1 = automático, o segundos (min 60)
Proxied bool `json:"proxied"` // true = nube naranja
Priority int `json:"priority,omitempty"` // Solo para MX / SRV
Comment string `json:"comment,omitempty"` // Comentario descriptivo
Tags []string `json:"tags,omitempty"` // Etiquetas (ej. ["owner:team"])
Type string `json:"type"` // A, AAAA, CNAME, TXT, MX, NS, SRV, CAA…
Name string `json:"name"` // Nombre del registro (ej. "www" o "@")
Content string `json:"content"` // Valor del registro
TTL int `json:"ttl"` // 1 = automático, o segundos (min 60)
Proxied bool `json:"proxied"` // true = nube naranja
Priority int `json:"priority,omitempty"` // Solo para MX / SRV
Comment string `json:"comment,omitempty"` // Comentario descriptivo
Tags []string `json:"tags,omitempty"` // Etiquetas (ej. ["owner:team"])
Settings *CFDNSRecordSettings `json:"settings,omitempty"` // Configuraciones adicionales
}