feat(cobro): suma el consumo de uMind al ciclo de cobro existente

El cobro recurrente (link de pago, webhook con firma, renovación
automática de vencimiento) ya funcionaba de punta a punta; lo único que
le faltaba para cobrar por uso era monto variable.

- MontoACobrar = PrecioAcordado + consumo pendiente del cliente. Si el
  cliente no usa uMind el consumo es 0 y el monto queda idéntico al de
  antes, así que los contratos existentes no cambian.
- El consumo se agrega por Cliente, no por tenant: un contrato es de un
  cliente y un cliente puede tener varios tenants.
- El desglose (cuántos tokens, imágenes y transcripciones, y a cuánto)
  va como líneas de servicio, así las plantillas de correo existentes lo
  muestran sin tocarlas. Cobrar un monto variable sin decir de dónde sale
  es pedir una disputa.
- Al confirmarse el pago se cierra el consumo (facturado_at). Solo si el
  UPDATE del contrato afectó filas: si el webhook llega dos veces, la
  segunda no vuelve a cerrar nada.
- Test de que el detalle del correo suma exactamente lo que se cobra.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-13 11:53:40 -05:00
co-authored by Claude Sonnet 5
parent 5b78f6677c
commit 023a8af494
4 changed files with 190 additions and 4 deletions
+5
View File
@@ -361,6 +361,11 @@ func MarcarContratoPagado(contratoID uint) (bool, error) {
result := app.Http.Database.DB.Model(&Contrato{}).
Where("id = ? AND pago_confirmado = false", contratoID).
Updates(updates)
if result.Error == nil && result.RowsAffected > 0 {
// Solo si el UPDATE realmente cambió algo: si el webhook llega dos
// veces, la segunda no entra acá y el consumo no se cierra de nuevo.
MarcarUsoFacturadoPorCliente(c.ClienteID)
}
return result.RowsAffected > 0, result.Error
}