> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Snapshots de consumo y medición

> Cuando esto se entregue, un tenant podrá ver qué ha consumido contra lo que contrató, actualizado cada 30 minutos, y podremos facturar desde los mismos números que él está mirando.

> Traducción. Autoritativo: [`fs-core-0010-usage-snapshots.md`](/modules/core/features/fs-core-0010-usage-snapshots).

## Contexto

DEC-G3 comprometió un pricing estilo Vercel: precios unitarios publicados, cuotas incluidas, consumo
medido, panel de gasto proyectado y spend caps configurables por el tenant. Todo eso descansa en una
medición lo bastante frecuente para ser accionable y lo bastante barata para correr por siempre para
cada tenant.

DEC-G1 lo fijó en **cada 30 minutos con deltas**. Calcular el consumo bajo demanda pondría una
consulta agregada en el camino de un dashboard; snapshots diarios dejarían a un tenant pasarse de una
cuota durante 23 horas antes de que alguien lo notara.

La medición tiene peso contractual: los ToS nombran estos snapshots como la base probatoria de la
facturación. Por eso son append-only y por eso una corrección es una fila nueva, nunca una edición.

## Alcance *(normativo)*

* `core.usage_snapshots`, append-only, particionada mensualmente, cada 30 minutos por tenant.
* `core.billable_metrics`: paramétrica — contactos accionables, eventos registrados, mensajes por
  canal, almacenamiento.
* Cálculo de delta entre t1 y t2, para que el consumo nunca requiera un scan.
* El conteo de contactos accionables, usando el predicado definido en FS-CORE-0004.
* Alertas de umbral a 80 / 90 / 100% por el propio sistema de notificaciones de la plataforma.
* Ganchos de enforcement: gracia, corte duro u overage por métrica, configurable por tenant desde Ops.

## Fuera de alcance *(normativo)*

* El rating engine — consume estos snapshots; es su propio epic (ADR-016).
* Facturación y cobro — Fintoc, Paddle, LibreDTE.
* La UI del panel de consumo — `frontend/console`.

## Comportamiento *(normativo)*

1. Los snapshots corren **cada 30 minutos para cada tenant**, por fan-out de cron. Sin tiers — el
   costo es bajo y un tenant que descubre un exceso un día tarde es un problema de soporte nuestro.
2. Cada fila guarda el **valor absoluto y el delta** desde el snapshot anterior. Los deltas hacen que
   facturar sea una suma en vez de un scan.
3. La tabla es **append-only**. Una corrección es una fila nueva con referencia `correction_of`;
   editar un snapshot destruiría el valor probatorio del que dependen los ToS.
4. Los contactos accionables usan el predicado de FS-CORE-0004 — una sola definición, compartida,
   para que medición y mensajería nunca discrepen sobre quién es facturable.
5. Las alertas a 80, 90 y 100% se envían por la cascada de notificaciones de la plataforma.
   Construir un segundo camino de entrega para nuestras propias alertas las pondría fuera de las
   reglas que todo lo demás obedece.
6. El enforcement por métrica es **configurable por tenant desde Softcrum Ops** (DEC-G2). Defaults:
   contactos gracia + aviso, mensajes duro al 110%, eventos soft con overage.
7. Un spend cap es la cara self-service del enforcement: alcanzarlo aplica el mismo corte que
   aplicaría el operador.
8. Una ventana de snapshot perdida se **registra como faltante, nunca se interpola**. Un número
   interpolado dentro de una base de facturación es un número que no podemos defender.
9. Particionada mensualmente; la retención sigue el plan, con el mínimo legal aplicando aparte.

## Datos *(normativo)*

| Tabla                   | Invariantes clave                                                                                                                                                                                        |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `core.usage_snapshots`  | append-only; `tenant_id`, `metric_code` FK, `absolute_value`, `delta`, `window_start`, `window_end`, `correction_of` nullable; único (`tenant_id`, `metric_code`, `window_end`); partición RANGE mensual |
| `core.billable_metrics` | paramétrica; semillas `is_system` `marketable_contacts`, `tracked_events`, `messages_email`, `messages_push`, `storage_bytes`; `unit`; no extensible por tenant                                          |

## API *(normativo)*

| Endpoint                        | Clase      | Permiso                | Presupuesto |
| ------------------------------- | ---------- | ---------------------- | ----------- |
| `GET /v1/core/usage`            | Management | `core.usage.read`      | p95 \<1 s   |
| `GET /v1/core/usage/projection` | Management | `core.usage.read`      | p95 \<1 s   |
| `PUT /v1/core/usage/spend-cap`  | Management | `core.usage.configure` | p95 \<1 s   |

## Eventos *(normativo)*

`core.usage.threshold_crossed` en el outbox al 80, 90 y 100%, que es lo que dispara la campaña de
alerta por `messaging`.

## Criterios de aceptación *(normativo)*

1. Los snapshots caen cada 30 minutos para cada tenant, verificado sobre una corrida sintética de 24
   horas.
2. La suma de deltas de un ciclo equivale a la diferencia de absolutos en sus extremos.
3. El conteo de contactos accionables coincide con la vista propia del módulo de mensajería para el
   mismo tenant e instante.
4. Cruzar el 80% emite una vez y no vuelve a emitir mientras siga por encima.
5. Una ventana perdida se registra como faltante y es visiblemente ausente, no interpolada.
6. Un spend cap aplica el enforcement configurado dentro de una ventana de snapshot.
7. Las correcciones se agregan con `correction_of` y dejan legible la original.
8. **Negativo:** ningún camino de código actualiza o borra una fila de snapshot.

## Ejecución

Pipeline asíncrono. Fan-out de cron en `backend/scheduler`, cálculo en `backend/workers`, endpoints
de lectura en `backend/api`.

## Preguntas abiertas

| # | Pregunta                                                                                        | Decide | Para             |
| - | ----------------------------------------------------------------------------------------------- | ------ | ---------------- |
| 1 | ¿El almacenamiento se mide por bytes en base de datos, por bytes de archivo exportado, o ambos? | Daniel | antes de aprobar |
| 2 | ¿Un spend cap detiene la Runtime API (rechazando `track`) o solo los efectos facturables?       | Daniel | antes de aprobar |

## Changelog

| Versión | Fecha      | Cambio           | Por qué | Autor                  |
| ------- | ---------- | ---------------- | ------- | ---------------------- |
| 0.1.0   | 2026-08-17 | Borrador inicial | —       | daniel + claude-opus-5 |

## Registro de entrega

*Aún no implementado.*
