Files
soft_usite/PLAN_RENOVACIONES.md
2026-04-29 23:36:30 -05:00

682 lines
23 KiB
Markdown

# 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
1. [Visión General](#1-visión-general)
2. [Arquitectura de Datos](#2-arquitectura-de-datos)
3. [Módulos del Sistema](#3-módulos-del-sistema)
4. [Flujo de Trabajo](#4-flujo-de-trabajo)
5. [Sistema de Notificaciones](#5-sistema-de-notificaciones)
6. [Plantillas de Correo](#6-plantillas-de-correo)
7. [Configuración SMTP](#7-configuración-smtp)
8. [Plan de Implementación por Fases](#8-plan-de-implementación-por-fases)
9. [Estructura de Archivos](#9-estructura-de-archivos)
10. [API Endpoints](#10-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
```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 | email | ✅ |
| 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 | email | |
| Contraseña | password (cifrada) | |
| Cifrado | select | TLS / SSL / Ninguno |
| Nombre remitente | texto | U-site |
| Email remitente | email | |
- 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.yml` en 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.
```go
// 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
```html
<!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/test` con 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`).
```go
// 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:
1. Descifra la contraseña
2. Actualiza `app.Http.Mail.*` en memoria
3. **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 `ProcesarVencimientos` con 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 a `go.mod`)
- **Preview correo:** `<iframe srcdoc>` + Alpine `x-data` con 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 por `cliente_id + fecha_vencimiento` antes de enviar
- **Doble envío:** antes de enviar cada grupo, consultar `notificaciones_log` filtrando `DATE(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ía `golang.org/x/text`
- **Auto-renovar:** el job crea el nuevo contrato con `fecha_inicio = fecha_vencimiento_anterior + 1 día` y la nueva `fecha_vencimiento` calculada según periodicidad del servicio
---
*Documento generado el 29 de abril de 2026 — U-site Admin System*