Files
FRASA-CAS/apps/cobranza/src/services/repository.ts
Consult AS Technology ecd4edac02 feat: ecosistema FRASA — apps Cotizador, Alta Clientes, Cobranza y Landing
- Cotizador (/quote): 4 propuestas con formulas validadas del Excel, comparador,
  combobox de unidades, vigencia, PDF ANEXO A
- Alta Clientes (/client): wizard 8 pasos, validacion CURP RENAPO, OCR de INE,
  checklist de expediente, firma digital, 5 PDFs legales
- Cobranza (/pay): planes de enganche, mora, vista cobranza del dia, recibos PDF,
  bitacora, dashboard con filtros
- Landing (/): entrada al ecosistema
- Design system StudioBlank (monocromo, acento #1B4332) en las 4 apps
- 123 tests vitest
2026-08-21 22:27:36 +00:00

116 lines
3.8 KiB
TypeScript

import type { PlanPago } from '../types'
import { generarSeed } from '../data/seed'
import { generarCalendario, registrarAbono, statusPlan, agregarNotaAPlan, type AbonoInput } from '../lib/cobranza'
import { uuid } from '../lib/uuid'
export const CLAVE_STORAGE = 'frasa_cobranza_v1'
// Inyectable para pruebas (stub en vitest, donde no hay localStorage)
type StorageLike = Pick<Storage, 'getItem' | 'setItem'>
function storageDefault(): StorageLike {
return globalThis.localStorage
}
/**
* Carga los planes desde localStorage; si está vacío siembra los datos de ejemplo.
* Migra planes guardados con versiones anteriores del modelo (sin `notas`).
* TODO: reemplazar por GET /api/planes cuando el API Gateway esté disponible.
*/
export function cargarPlanes(storage: StorageLike = storageDefault()): PlanPago[] {
const crudo = storage.getItem(CLAVE_STORAGE)
if (crudo) {
const planes = JSON.parse(crudo) as PlanPago[]
return planes.map((p) => (p.notas ? p : { ...p, notas: [] }))
}
const planes = generarSeed()
guardarPlanes(planes, storage)
return planes
}
export function guardarPlanes(planes: PlanPago[], storage: StorageLike = storageDefault()): void {
storage.setItem(CLAVE_STORAGE, JSON.stringify(planes))
}
export function obtenerPlan(id: string, storage: StorageLike = storageDefault()): PlanPago | undefined {
return cargarPlanes(storage).find((p) => p.id === id)
}
export interface NuevoPlanInput {
clienteNombre: string
clienteTelefono?: string
unidadCodigo: string
proyecto: string
ventaRef: string
fechaInicio: string
engancheTotal: number
numPagos: number
tasaMoraMensual?: number
}
export function crearPlan(input: NuevoPlanInput, storage: StorageLike = storageDefault()): PlanPago {
const { mensualidad, pagos } = generarCalendario(input.fechaInicio, input.engancheTotal, input.numPagos)
const plan: PlanPago = {
id: uuid(),
ventaRef: input.ventaRef,
clienteNombre: input.clienteNombre,
clienteTelefono: input.clienteTelefono || undefined,
unidadCodigo: input.unidadCodigo,
proyecto: input.proyecto,
fechaInicio: input.fechaInicio,
engancheTotal: input.engancheTotal,
numPagos: input.numPagos,
mensualidad,
tasaMoraMensual: input.tasaMoraMensual,
status: 'ACTIVO',
pagos,
notas: [],
createdAt: new Date().toISOString(),
}
guardarPlanes([...cargarPlanes(storage), plan], storage)
return plan
}
export function actualizarPlan(plan: PlanPago, storage: StorageLike = storageDefault()): PlanPago {
// El status operativo (ACTIVO/LIQUIDADO) se deriva del saldo; CANCELADO es manual
const actualizado: PlanPago = { ...plan, status: statusPlan(plan) }
guardarPlanes(
cargarPlanes(storage).map((p) => (p.id === actualizado.id ? actualizado : p)),
storage,
)
return actualizado
}
export function cancelarPlan(id: string, storage: StorageLike = storageDefault()): PlanPago {
const plan = obtenerPlan(id, storage)
if (!plan) throw new Error('Plan no encontrado')
const cancelado: PlanPago = { ...plan, status: 'CANCELADO' }
guardarPlanes(
cargarPlanes(storage).map((p) => (p.id === id ? cancelado : p)),
storage,
)
return cancelado
}
export function registrarAbonoEnPlan(
planId: string,
abono: Omit<AbonoInput, 'pagoId'>,
pagoId: string,
storage: StorageLike = storageDefault(),
): PlanPago {
const plan = obtenerPlan(planId, storage)
if (!plan) throw new Error('Plan no encontrado')
return actualizarPlan(registrarAbono(plan, { ...abono, pagoId }), storage)
}
/** Agrega una nota a la bitácora del plan y persiste el cambio */
export function agregarNota(
planId: string,
texto: string,
storage: StorageLike = storageDefault(),
): PlanPago {
const plan = obtenerPlan(planId, storage)
if (!plan) throw new Error('Plan no encontrado')
return actualizarPlan(agregarNotaAPlan(plan, texto), storage)
}