feat: mis cobranzas, recordatorios de pago y agente IA con acciones

- PlanPago.vendedorId + filtro ?mios=1; toggle Mis planes/Todos en /pay
- Motor de recordatorios: próximos a vencer (3d) y vencidos (re-cada 7d),
  canal Monday Actividad + stub WhatsApp listo para WA CRM, log persistente,
  cron diario 9:30, run manual con dry-run, historial en /api/cobranza/recordatorios
- Agente IA: memoria por usuario (conversaciones aisladas) y tool calling:
  cotizar_unidad (motor real por desarrollo) y buscar_unidad (inventario Monday)
- Tests: api 261, cobranza 75, agente-ia 13
This commit is contained in:
2026-08-28 08:07:12 +00:00
parent 59944668a8
commit aa27e89c75
18 changed files with 1437 additions and 17 deletions

View File

@@ -104,11 +104,15 @@ model PlanPago {
// Item del tablero Apartado (Monday) ligado a este plan; al liquidarse el
// plan, ese item pasa a 'X Escriturar' (puente cobranza → postventa).
mondayItemId String?
// Vendedor dueño del plan (sub del JWT al crearlo); null en planes históricos.
// Alimenta el filtro "mis cobranzas" y la ruta de recordatorios al vendedor.
vendedorId String?
createdAt DateTime @default(now())
notas NotaPlan[]
pagos Pago[]
@@index([unidadCodigo])
@@index([vendedorId])
}
model Pago {
@@ -144,6 +148,23 @@ model AutomatizacionState {
updatedAt DateTime @updatedAt
}
// Bitácora de recordatorios de pago enviados por el motor de cobranza
// (recordatorios-cobranza.service). Sirve a la vez de dedup (no repetir el
// mismo recordatorio el mismo día / respetar la ventana de re-recordatorio) y
// de historial consultable por GET /api/cobranza/recordatorios.
model RecordatorioLog {
id String @id @default(uuid())
pagoId String
tipo String // PROXIMO_VENCER | VENCIDO
canal String // monday-actividad | whatsapp | log
destinatario String // mondayUserId del responsable o 'sin-destinatario'
mensaje String
createdAt DateTime @default(now())
@@index([pagoId])
@@index([createdAt])
}
model NotaPlan {
id String @id @default(uuid())
planId String
@@ -200,8 +221,13 @@ model AIChunk {
model AIConversacion {
id String @id @default(uuid())
// Dueño de la conversación (sub del JWT); null en anónimas/históricas.
// GET /api/ai/conversations devuelve solo las del usuario (admin: todas).
usuarioId String?
createdAt DateTime @default(now())
mensajes AIMensaje[]
@@index([usuarioId])
}
model AIMensaje {