23 KiB
Plan de Trabajo — Módulo de Renovaciones y Contratos
Sistema completo de gestión de servicios, clientes, contratos y notificaciones automáticas de vencimiento.
Índice
- Visión General
- Arquitectura de Datos
- Módulos del Sistema
- Flujo de Trabajo
- Sistema de Notificaciones
- Plantillas de Correo
- Configuración SMTP
- Plan de Implementación por Fases
- Estructura de Archivos
- API Endpoints
1. Visión General
El módulo de Renovaciones permite a la empresa gestionar el ciclo de vida completo de sus servicios vendidos a clientes: creación de catálogo, asignación a clientes con fechas de vencimiento, envío automático y manual de correos de cobro/renovación, y configuración total de notificaciones programadas.
Capacidades principales
| Capacidad | Descripción |
|---|---|
| Catálogo de servicios | Crear servicios con precio, tipo (renovable/único), periodicidad |
| Gestión de clientes | CRUD completo de clientes con datos de contacto |
| Contratos / Asignaciones | Relacionar clientes con servicios, definir fechas de inicio y vencimiento |
| Agrupación de correos | Si varios servicios vencen la misma fecha → un solo correo con el total |
| Notificaciones programadas | Configurar N días antes del vencimiento, con cron interno |
| Editor de plantillas | Editor visual con preview en tiempo real |
| Configuración SMTP | Panel para cambiar servidor de correo sin reiniciar |
2. Arquitectura de Datos
Modelo Entidad-Relación
clientes ─────────── contratos ─────────── servicios
│ │ │
│ ├── fecha_inicio ├── tipo: renovable / unico
│ ├── fecha_vencimiento ├── precio
│ ├── estado ├── periodicidad (mensual/anual/etc)
│ └── notas └── descripcion
│
└── email, telefono, empresa, ...
contratos ──── notificaciones_log
├── fecha_envio
├── tipo (aviso / vencimiento / recordatorio)
└── estado (enviado / fallido / pendiente)
smtp_config (tabla singleton)
plantillas_correo
notificacion_reglas (días antes, activo, plantilla_id)
Tablas SQL
-- Servicios / Productos
CREATE TABLE servicios (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
nombre TEXT NOT NULL,
descripcion TEXT,
precio NUMERIC(12,2) NOT NULL DEFAULT 0,
moneda TEXT NOT NULL DEFAULT 'COP',
tipo TEXT NOT NULL DEFAULT 'renovable', -- 'renovable' | 'unico'
periodicidad TEXT, -- 'mensual' | 'trimestral' | 'semestral' | 'anual' | NULL
activo BOOLEAN NOT NULL DEFAULT true
);
-- Clientes
CREATE TABLE clientes (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
nombre TEXT NOT NULL,
empresa TEXT,
email TEXT NOT NULL,
email_cc TEXT, -- correos adicionales separados por coma
telefono TEXT,
documento TEXT,
notas TEXT,
activo BOOLEAN NOT NULL DEFAULT true
);
-- Contratos / Asignaciones
CREATE TABLE contratos (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
cliente_id BIGINT NOT NULL REFERENCES clientes(id),
servicio_id BIGINT NOT NULL REFERENCES servicios(id),
fecha_inicio DATE NOT NULL,
fecha_vencimiento DATE NOT NULL,
precio_acordado NUMERIC(12,2), -- puede diferir del precio base
estado TEXT NOT NULL DEFAULT 'activo', -- 'activo' | 'vencido' | 'cancelado' | 'renovado'
auto_renovar BOOLEAN NOT NULL DEFAULT false,
notas TEXT
);
-- Reglas de notificación (configurable por días antes del vencimiento)
CREATE TABLE notificacion_reglas (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
nombre TEXT NOT NULL, -- ej: "Aviso 30 días antes"
dias_antes INT NOT NULL, -- ej: 30, 15, 7, 1
plantilla_id BIGINT REFERENCES plantillas_correo(id),
activo BOOLEAN NOT NULL DEFAULT true,
aplica_a TEXT NOT NULL DEFAULT 'todos' -- 'todos' | 'renovable' | 'unico'
);
-- Plantillas de correo
CREATE TABLE plantillas_correo (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
nombre TEXT NOT NULL,
asunto TEXT NOT NULL,
cuerpo_html TEXT NOT NULL, -- soporta variables: {{.ClienteNombre}}, {{.Servicios}}, {{.Total}}, etc.
tipo TEXT NOT NULL DEFAULT 'renovacion' -- 'renovacion' | 'vencimiento' | 'pago' | 'personalizado'
);
-- Log de notificaciones enviadas
CREATE TABLE notificaciones_log (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ,
cliente_id BIGINT REFERENCES clientes(id),
regla_id BIGINT REFERENCES notificacion_reglas(id),
contratos_ids TEXT, -- JSON array de IDs agrupados
fecha_envio TIMESTAMPTZ,
estado TEXT NOT NULL DEFAULT 'pendiente', -- 'enviado' | 'fallido' | 'pendiente'
error_msg TEXT,
asunto TEXT,
preview_html TEXT -- copia del correo enviado
);
-- Configuración SMTP (singleton, solo 1 fila activa)
CREATE TABLE smtp_config (
id BIGSERIAL PRIMARY KEY,
updated_at TIMESTAMPTZ,
host TEXT NOT NULL,
port INT NOT NULL DEFAULT 587,
username TEXT NOT NULL,
password TEXT NOT NULL, -- almacenado cifrado (AES-256)
encryption TEXT NOT NULL DEFAULT 'tls', -- 'tls' | 'ssl' | 'none'
from_address TEXT NOT NULL,
from_name TEXT NOT NULL,
activo BOOLEAN NOT NULL DEFAULT true
);
3. Módulos del Sistema
3.1 Catálogo de Servicios (/app/servicios)
Funcionalidades:
- Listar servicios con filtro por tipo y estado
- Crear/editar/eliminar servicio
- Campos: nombre, descripción, precio, moneda, tipo (renovable | único), periodicidad
- Badge visual diferenciando renovables de únicos
- Indicador de cuántos contratos activos tiene cada servicio
Campos del formulario:
| Campo | Tipo | Requerido | Notas |
|---|---|---|---|
| Nombre | texto | ✅ | |
| Descripción | textarea | ❌ | |
| Precio | decimal | ✅ | |
| Moneda | select | ✅ | COP / USD / EUR |
| Tipo | radio | ✅ | Renovable / Único |
| Periodicidad | select | si renovable | Mensual / Trimestral / Semestral / Anual |
| Activo | toggle | ✅ |
3.2 Clientes (/app/clientes)
Funcionalidades:
- CRUD completo de clientes
- Vista detalle del cliente con todos sus contratos activos/vencidos
- Historial de correos enviados al cliente
- Múltiples emails CC por cliente
Campos del formulario:
| Campo | Tipo | Requerido |
|---|---|---|
| Nombre completo | texto | ✅ |
| Empresa / Razón social | texto | ❌ |
| Email principal | ✅ | |
| Emails CC | texto (separados por coma) | ❌ |
| Teléfono / WhatsApp | texto | ❌ |
| Documento (NIT/CC) | texto | ❌ |
| Notas internas | textarea | ❌ |
| Activo | toggle | ✅ |
3.3 Contratos / Asignaciones (/app/contratos)
Vista principal:
- Tabla con columnas: Cliente, Servicio, Fecha inicio, Fecha vencimiento, Estado, Días restantes
- Filtros: por cliente, por servicio, por estado, por rango de fechas
- Indicador visual de urgencia (verde > 30 días, amarillo 1-30 días, rojo vencido)
- Botón "Renovar" que crea un nuevo contrato a partir del vencido
Formulario de asignación:
| Campo | Tipo | Notas |
|---|---|---|
| Cliente | select buscable | autocompletado |
| Servicio | select buscable | muestra precio base |
| Fecha inicio | date | default: hoy |
| Fecha vencimiento | date | calculada automáticamente según periodicidad |
| Precio acordado | decimal | editable, pre-rellena con precio del servicio |
| Auto-renovar | toggle | crea nuevo contrato automáticamente al vencer |
| Notas | textarea |
Vista detalle del contrato:
- Historial de renovaciones anteriores
- Timeline de notificaciones enviadas
- Botón "Enviar correo manual"
- Botón "Renovar ahora"
3.4 Reglas de Notificación (/app/notificaciones/reglas)
Permite configurar cuándo y qué correo enviar antes del vencimiento.
Ejemplos de reglas:
| Regla | Días antes | Plantilla | Aplica a |
|---|---|---|---|
| Aviso temprano | 30 | Plantilla "Renovación próxima" | Renovables |
| Recordatorio | 7 | Plantilla "Vence en 7 días" | Todos |
| Último aviso | 1 | Plantilla "Vence mañana" | Todos |
| Vencido | 0 | Plantilla "Servicio vencido" | Todos |
Campos:
| Campo | Tipo | Notas |
|---|---|---|
| Nombre de la regla | texto | |
| Días antes del vencimiento | número | 0 = día del vencimiento |
| Plantilla de correo | select | |
| Aplica a | select | Todos / Solo renovables / Solo únicos |
| Activo | toggle |
3.5 Plantillas de Correo (/app/notificaciones/plantillas)
Editor visual con:
- Editor de HTML (textarea con resaltado)
- Panel de variables disponibles (click to insert)
- Preview en tiempo real lado a lado
- Envío de correo de prueba a email específico
Variables disponibles en plantillas:
{{.ClienteNombre}} → Nombre del cliente
{{.ClienteEmpresa}} → Empresa del cliente
{{.Servicios}} → Tabla HTML de servicios (nombre, vencimiento, precio)
{{.Total}} → Total en moneda local
{{.FechaVencimiento}} → Fecha de vencimiento (la más próxima si hay varias)
{{.DiasRestantes}} → Días que faltan para vencer
{{.LinkPago}} → URL de pago (configurable)
{{.EmpresaNombre}} → Nombre de la empresa (desde config)
{{.FechaActual}} → Fecha de hoy
Tipo de plantillas:
| Tipo | Uso |
|---|---|
renovacion |
Aviso de renovación próxima |
vencimiento |
El servicio está a punto de vencer / vencido |
pago |
Confirmación o solicitud de pago |
personalizado |
Uso libre / envío manual |
3.6 Historial de Envíos (/app/notificaciones/historial)
- Tabla con todos los correos enviados
- Filtro por cliente, fecha, estado (enviado/fallido)
- Ver HTML del correo enviado (modal preview)
- Reenviar correo fallido con un clic
3.7 Configuración SMTP (/app/configuracion/smtp)
Panel para configurar el servidor de correo saliente sin tocar archivos.
Campos:
| Campo | Tipo | Default |
|---|---|---|
| Host SMTP | texto | smtp.gmail.com |
| Puerto | número | 587 |
| Usuario | ||
| Contraseña | password (cifrada) | |
| Cifrado | select | TLS / SSL / Ninguno |
| Nombre remitente | texto | U-site |
| Email remitente |
- Botón "Probar conexión" — envía correo de prueba y muestra resultado
- La config se guarda en BD (cifrada) y sobrescribe la del
config.ymlen runtime
4. Flujo de Trabajo
Flujo de creación de un contrato
1. Crear servicio en catálogo
↓
2. Crear/seleccionar cliente
↓
3. Crear contrato (asignar servicio + fechas + precio)
↓
4. Sistema calcula automáticamente
cuándo disparar notificaciones
según reglas configuradas
↓
5. Cron diario evalúa contratos
y dispara correos agrupados
Lógica de agrupación de correos
Para cada cliente con contratos próximos a vencer:
│
├── mismo_dia = contratos donde fecha_vencimiento == misma fecha
│ └── → UN solo correo con tabla de servicios + total sumado
│
└── fechas_distintas = contratos con fechas diferentes
└── → UN correo por cada fecha de vencimiento distinta
Ejemplo práctico:
- Cliente "Empresa ABC" tiene:
- Dominio → vence 15 mayo → correo independiente
- Hosting + SSL → ambos vencen 15 mayo → un solo correo con total combinado
- Mantenimiento → vence 30 mayo → correo independiente
5. Sistema de Notificaciones
Arquitectura del Cron
El sistema usa un cron interno en Go (librería robfig/cron) que se inicia al arrancar la aplicación.
// Se ejecuta diariamente a las 8:00 AM
cron.AddFunc("0 8 * * *", jobs.ProcesarVencimientos)
Job: ProcesarVencimientos
1. Obtener todas las reglas activas (ordenadas por dias_antes)
2. Para cada regla:
a. Buscar contratos donde:
- estado = 'activo'
- fecha_vencimiento = HOY + dias_antes
- NO exista ya un envío del mismo tipo en notificaciones_log
b. Agrupar contratos encontrados por cliente + fecha_vencimiento
c. Para cada grupo:
- Renderizar plantilla con datos del grupo
- Enviar correo (con CC si aplica)
- Registrar en notificaciones_log
3. Si auto_renovar = true y estado = vencido:
- Crear nuevo contrato con nueva fecha de vencimiento
- Actualizar estado del anterior a 'renovado'
Estados de un contrato
activo ──────────── (cron notifica) ──────────── vence
│ │
├── renovado (si auto_renovar o manual) │
│ │
└── cancelado (manual) vencido
6. Plantillas de Correo
Variables en el cuerpo HTML
El motor de plantillas usa html/template de Go (ya usado en el proyecto).
Plantilla base sugerida — Renovación
<!DOCTYPE html>
<html>
<body style="font-family:Inter,sans-serif; background:#f8fafc; padding:20px">
<div style="max-width:600px; margin:0 auto; background:white; border-radius:12px; overflow:hidden">
<!-- Header -->
<div style="background:#1e293b; padding:24px; text-align:center">
<h1 style="color:#8eb02f; margin:0">U-site</h1>
</div>
<!-- Body -->
<div style="padding:32px">
<p>Hola <strong>{{.ClienteNombre}}</strong>,</p>
<p>Te recordamos que los siguientes servicios vencen próximamente:</p>
<!-- Tabla de servicios -->
<table style="width:100%; border-collapse:collapse; margin:20px 0">
<thead>
<tr style="background:#f1f5f9">
<th style="padding:10px; text-align:left">Servicio</th>
<th style="padding:10px; text-align:right">Vencimiento</th>
<th style="padding:10px; text-align:right">Valor</th>
</tr>
</thead>
<tbody>
{{range .Servicios}}
<tr>
<td style="padding:10px; border-bottom:1px solid #e2e8f0">{{.Nombre}}</td>
<td style="padding:10px; border-bottom:1px solid #e2e8f0; text-align:right">{{.FechaVencimiento}}</td>
<td style="padding:10px; border-bottom:1px solid #e2e8f0; text-align:right">{{.Precio}}</td>
</tr>
{{end}}
</tbody>
<tfoot>
<tr>
<td colspan="2" style="padding:10px; font-weight:bold">Total</td>
<td style="padding:10px; font-weight:bold; text-align:right; color:#8eb02f">{{.Total}}</td>
</tr>
</tfoot>
</table>
<p style="text-align:center; margin:30px 0">
<a href="{{.LinkPago}}" style="background:#8eb02f; color:white; padding:14px 28px; border-radius:8px; text-decoration:none; font-weight:bold">
Renovar ahora →
</a>
</p>
</div>
<!-- Footer -->
<div style="background:#f8fafc; padding:16px; text-align:center; font-size:12px; color:#64748b">
© 2026 U-site — {{.EmpresaNombre}}
</div>
</div>
</body>
</html>
Preview en tiempo real
- Frontend: Alpine.js +
<iframe srcdoc="...">actualizado en cada keystroke con debounce de 500ms - El preview sustituye las variables por datos de ejemplo configurables
- Botón "Enviar prueba" → POST
/app/notificaciones/plantillas/:id/testcon email destino
7. Configuración SMTP
Almacenamiento seguro
La contraseña SMTP se cifra con AES-256-GCM antes de guardar en BD, usando el app_jwt_secret como clave de cifrado (ya disponible en config.yml).
// Guardar: cifrar contraseña antes de INSERT
encrypted := utils.Encrypt(password, app.Http.Token.AppJwtSecret)
// Usar: descifrar antes de conectar
plain := utils.Decrypt(encrypted, app.Http.Token.AppJwtSecret)
Override en runtime
Al guardar una nueva configuración SMTP el sistema:
- Descifra la contraseña
- Actualiza
app.Http.Mail.*en memoria - No requiere reiniciar el servidor
8. Plan de Implementación por Fases
Fase 1 — Modelos y migración de BD (~1-2 días)
- Crear modelos Go:
Servicio,Cliente,Contrato,NotificacionRegla,PlantillaCorreo,NotificacionLog,SmtpConfig - Agregar al
Migrate()todas las nuevas tablas - Seed inicial: 1 plantilla por defecto, reglas básicas (30, 7, 1 día)
Fase 2 — CRUD Servicios y Clientes (~2 días)
- Controller + rutas para Servicios
- Controller + rutas para Clientes
- Vistas HTML: lista, formulario (modal), detalle
- Búsqueda y paginación
Fase 3 — Contratos / Asignaciones (~2-3 días)
- Controller + rutas para Contratos
- Select2 para cliente y servicio con autocompletado AJAX
- Cálculo automático de fecha de vencimiento según periodicidad
- Vista principal con indicadores de color por días restantes
- Botón renovar (clona contrato con nueva fecha)
- Historial de renovaciones
Fase 4 — Plantillas de correo con preview (~2 días)
- CRUD de plantillas
- Editor HTML con preview en
<iframe srcdoc> - API
/plantillas/:id/preview→ renderiza con datos de ejemplo - Endpoint
/plantillas/:id/test→ envía correo de prueba
Fase 5 — Reglas de notificación y Cron (~2 días)
- CRUD de reglas
- Job
ProcesarVencimientoscon lógica de agrupación - Inicializar cron al arrancar (
app.go) - Evitar doble envío (check en
notificaciones_log)
Fase 6 — Historial y reenvío (~1 día)
- Vista historial de envíos con filtros
- Modal preview del correo enviado
- Botón reenviar
Fase 7 — Configuración SMTP (~1 día)
- Panel CRUD de configuración SMTP
- Cifrado AES de contraseña
- Override en runtime de
app.Http.Mail - Botón "Probar conexión"
Fase 8 — Integración al menú y permisos (~0.5 días)
- Agregar módulo "Renovaciones" al sidebar
- Asignar permisos RBAC (Casbin)
- Registrar módulo y submodules en BD
9. Estructura de Archivos
pkg/
models/
servicio.go
cliente.go
contrato.go
notificacion_regla.go
plantilla_correo.go
notificacion_log.go
smtp_config.go
rest/
controllers/
servicio_controller.go
cliente_controller.go
contrato_controller.go
notificacion_controller.go
plantilla_controller.go
smtp_config_controller.go
routes/
renovaciones.go ← agrupa todas las rutas del módulo
pkg/
services/
renovacion_service.go ← lógica de agrupación y envío
cron_service.go ← job diario de vencimientos
resources/views/
servicios/
index.html
form.html
clientes/
index.html
form.html
detalle.html
contratos/
index.html
form.html
detalle.html
notificaciones/
reglas.html
plantillas.html
historial.html
editor.html ← editor con preview
configuracion/
smtp.html
emails/
renovacion.html ← plantilla base de correo
vencimiento.html
pago.html
10. API Endpoints
Servicios
| Método | Ruta | Descripción |
|---|---|---|
| GET | /app/servicios |
Lista de servicios |
| GET | /app/loadservicios |
JSON para DataTable |
| POST | /app/servicios |
Crear servicio |
| PUT | /app/servicios/:id |
Actualizar |
| DELETE | /app/servicios/:id |
Eliminar |
Clientes
| Método | Ruta | Descripción |
|---|---|---|
| GET | /app/clientes |
Lista de clientes |
| GET | /app/loadclientes |
JSON para DataTable / Select2 |
| GET | /app/clientes/:id |
Detalle con contratos |
| POST | /app/clientes |
Crear |
| PUT | /app/clientes/:id |
Actualizar |
| DELETE | /app/clientes/:id |
Eliminar |
Contratos
| Método | Ruta | Descripción |
|---|---|---|
| GET | /app/contratos |
Lista con filtros |
| GET | /app/loadcontratos |
JSON para DataTable |
| POST | /app/contratos |
Crear asignación |
| PUT | /app/contratos/:id |
Actualizar |
| POST | /app/contratos/:id/renovar |
Renovar contrato |
| POST | /app/contratos/:id/correo |
Envío manual de correo |
| DELETE | /app/contratos/:id |
Cancelar |
Notificaciones / Plantillas
| Método | Ruta | Descripción |
|---|---|---|
| GET | /app/notificaciones/reglas |
Lista de reglas |
| POST | /app/notificaciones/reglas |
Crear regla |
| PUT | /app/notificaciones/reglas/:id |
Actualizar |
| DELETE | /app/notificaciones/reglas/:id |
Eliminar |
| GET | /app/notificaciones/plantillas |
Lista de plantillas |
| POST | /app/notificaciones/plantillas |
Crear |
| PUT | /app/notificaciones/plantillas/:id |
Actualizar |
| POST | /app/notificaciones/plantillas/:id/preview |
Renderiza con datos de ejemplo |
| POST | /app/notificaciones/plantillas/:id/test |
Enviar correo de prueba |
| GET | /app/notificaciones/historial |
Log de envíos |
| POST | /app/notificaciones/historial/:id/reenviar |
Reenviar |
SMTP
| Método | Ruta | Descripción |
|---|---|---|
| GET | /app/configuracion/smtp |
Ver configuración actual |
| POST | /app/configuracion/smtp |
Guardar configuración |
| POST | /app/configuracion/smtp/test |
Probar conexión |
Notas Técnicas
- Cron: usar
github.com/robfig/cron/v3(agregar ago.mod) - Preview correo:
<iframe srcdoc>+ Alpinex-datacon debounce — sin backend para el preview inicial, JS reemplaza variables con datos de ejemplo en el cliente - Agrupación de correos: lógica en
renovacion_service.go, agrupa porcliente_id + fecha_vencimientoantes de enviar - Doble envío: antes de enviar cada grupo, consultar
notificaciones_logfiltrandoDATE(created_at) = TODAY AND cliente_id = X AND regla_id = Y AND contratos_ids CONTAINS Z - Moneda: formatear con
fmt.Sprintf("$ %,.2f", valor)o libreríagolang.org/x/text - Auto-renovar: el job crea el nuevo contrato con
fecha_inicio = fecha_vencimiento_anterior + 1 díay la nuevafecha_vencimientocalculada según periodicidad del servicio
Documento generado el 29 de abril de 2026 — U-site Admin System