Cuentas, pedidos y créditos
Comprende cómo se relacionan los usuarios, los pedidos y el libro mayor de créditos en el template Sushi SaaS. Aprende la fórmula del saldo, el manejo de caducidad y las APIs/servicios para otorgar, consumir y consultar créditos.
Por qué importa
Este template incluye autenticación, seguimiento de pedidos compatible con Stripe y un sistema de saldo. Comprender cómo se relacionan las tablas te permitirá ampliar el catálogo o conectar tu propio proveedor de pagos.
El reparto (tablas clave)
users— Fuente de verdad del perfil.uuid(clave común), email, locale, rol. Poblado por Better Auth.accountsysessions— Internas de Better Auth para inicio de sesión. Útiles para auditoría; fuera del cálculo del saldo.orders— Cada intento/resultado de compra.order_no,status,amount, columnas de suscripción (sub_*),credits,expired_at.credits— Libro mayor append‑only. Positivos = otorgamientos; negativos = consumos. Columnas:trans_no,user_uuid,trans_type,credits,order_no,expired_at.tasks— Trabajos de uso (p. ej., texto→video).credits_used,credits_trans_noenlazan el gasto con la tarea.- Cameos:
apikeys,affiliatestambién cuelgan deuser_uuid.
Atajos de relación (sin FKs, pero estables):
users.uuid→orders.user_uuid|credits.user_uuid|tasks.user_uuid|apikeys.user_uuid|affiliates.user_uuidorders.order_no→credits.order_nocredits.trans_no→tasks.credits_trans_no
Todas las tablas tienen timestamps para reconstruir la historia y hacer analíticas.
La analogía: Billetera + Recibos
- Piensa en
creditscomo una billetera transparente. Cada depósito o gasto es un recibo que guardas. - Los depósitos (filas positivas) suman dinero; los gastos (negativas) lo restan. ¿Tarjeta regalo vencida? El recibo sigue, pero ya no paga nada.
- El saldo = suma de positivos no vencidos − suma de negativos.
Por eso el libro es append‑only: no mutamos el pasado, añadimos nuevos recibos para explicar todo.
Una acción → varias filas (recorridos)
Pago exitoso (Stripe)
orders: la filaorder_nopasa apaidcon metadatos del cargo.credits: añadimos una fila positiva{ trans_type: "order_pay", credits: <order.credits>, order_no }.affiliates: actualización opcional de recompensa enlazada al pedido.
Gasto en una tarea (Texto→Video)
credits: fila negativa{ trans_type: "task_text_to_video", credits: -N }y devolvemos sutrans_no.tasks: fila{ credits_used: N, credits_trans_no: <ese trans_no> }para trazar el gasto.
Mini ping (1 crédito)
credits: fila negativa{ trans_type: "ping", credits: -1 }.
Caducidad
- Una fila positiva con
expired_aten el pasado ya no suma al balance, pero queda para auditoría.
Servicios y APIs útiles
src/services/credit.ts#getUserCreditSummary—balance,granted,consumed,expired,expiringSoon[],ledger[](recortado).src/services/credit.ts#getUserCredits— snapshot ligero para gating:{ left_credits, is_pro, is_recharged }.src/services/credit.ts#decreaseCredits— agrega fila negativa; lanza error si faltan créditos.src/services/credit.ts#increaseCredits— agrega fila positiva; usado por Stripe y otorgamientos.src/services/stripe.ts#handleCheckoutSession— convierte una sesión pagada en estado del pedido + fila de crédito.src/services/tasks.ts#createTextToVideoTask— descuenta créditos y crea una filatasksque referencia ese gasto.
Endpoints HTTP
POST /api/account/credits— resumen; acepta{ includeLedger, ledgerLimit, includeExpiring }.POST /api/account/profile— perfil + resumen;{ includeCreditLedger: false }para aligerar.POST /api/ping— demo de consumo (1 crédito por defecto).
Todas responden con respJson: { code, message, data }.
Prueba el flujo en local
-
Inicia la app:
pnpm dev(requiere una URL de Postgres en.env.local). -
Crea un usuario: visita
/es/signup, regístrate e inicia sesión. -
Añade créditos manualmente (modo desarrollo) insertando en
credits:insert into credits (trans_no, user_uuid, trans_type, credits, created_at) values ('dev-grant-1', '<uuid-del-usuario>', 'manual_adjustment', 100, now());Puedes ejecutarlo con
pnpm drizzle-kit studioo tu cliente SQL favorito. -
Consulta el saldo: llama a
/api/account/creditscon HTTPie o curl:curl -X POST http://localhost:3000/api/account/credits \ -H "Content-Type: application/json" \ -H "Cookie: <copia las cookies de autenticación>" -
Consume créditos: llama a
/api/pingcon un mensaje para descontarCreditsAmount.PingCost(1 crédito por defecto) y vuelve a consultar el saldo.curl -X POST http://localhost:3000/api/ping \ -H "Content-Type: application/json" \ -H "Cookie: <copia las cookies de autenticación>" \ -d '{\"message\":\"hola\"}'
Personaliza el sistema
Cambia la ventana de caducidad
Ajusta EXPIRING_WINDOW_DAYS en src/services/credit.ts para modificar el rango de aviso “expira pronto”. Si quieres forzar la caducidad automática, crea un cron que añada ajustes negativos cuando expired_at haya pasado.
Bonos de bienvenida
Usa insertCredit de src/models/credit.ts justo después de insertUser para dar saldo inicial. Asegúrate de mantener único trans_no.
Integra otro procesador de pagos
- El template asume Stripe, pero puedes llamar a
insertOrdercon la carga de tu proveedor. - Registra los créditos en
creditsy enlaza elorder_nopara facilitar las conciliaciones. - Actualiza la UI o la documentación si utilizas las columnas de suscripción (
sub_*).
Guarda más contexto
Agrega columnas (por ejemplo meta en JSON) a credits/orders para flags o experimentos. El sistema de migraciones de Drizzle actualizará el snapshot automáticamente.
Trucos y gotchas
- El balance son sumas; no “gastamos” una fila positiva concreta. Las negativas referencian una fuente probable vía
order_nopara trazabilidad. getUserValidCreditsordena porexpired_atpara etiquetar una fuente cercana al consumir.- ¿Necesitas FIFO estricto o consumos por lote? Extiende el modelo y conserva el libro append‑only para auditoría.
- Mantén
users.uuidestable; es el pegamento entre orders, credits, tasks, keys y affiliates.
Próximos pasos
- Crea dashboards usando el resumen de
getUserCreditSummary. - Usa el endpoint
/api/pingcomo plantilla para otras operaciones que consuman créditos. - Escribe pruebas unitarias para la lógica de créditos; simula operaciones y valida
balance,expiredyexpiringSoon. - ¿Quieres esta guía en otro idioma? Puedo sincronizarla.
Configuración de la base de datos
Configura Postgres y Drizzle ORM — la columna vertebral para auth, cobros, almacenamiento, tareas y tablas de la app.
Tareas con Créditos — Texto a Video
Añade monetización por uso con una tabla genérica de tareas, un libro mayor de créditos y un generador de texto a video enchufable. Aprende el esquema, APIs, constantes de configuración y una UI mínima para lanzar funciones de IA.