Del look editorial (verde #1B4332, esquinas a 0, botones en mayusculas) al sistema NovaSpark: primario azul #2563EB, detalle naranja #F97316, fondo #F8FAFC con tarjetas blancas rounded-xl + shadow-sm, chips rounded-full semanticos, focus ring azul, transiciones 100-200ms. Incluye headers de tabla en los PDFs (cotizador y estado de cuenta).
46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
import type { PlanPago } from '../types'
|
|
import { diasParaProximoVencimiento, statusPago, statusPlan } from '../lib/cobranza'
|
|
|
|
export type Semaforo = 'VERDE' | 'AMBAR' | 'ROJO' | 'GRIS'
|
|
|
|
const ETIQUETA: Record<Semaforo, string> = {
|
|
VERDE: 'Al corriente',
|
|
AMBAR: 'Por vencer / parcial',
|
|
ROJO: 'Vencido',
|
|
GRIS: 'Cerrado',
|
|
}
|
|
|
|
const CLASE_DOT: Record<Semaforo, string> = {
|
|
VERDE: 'bg-green-50 text-green-700',
|
|
AMBAR: 'bg-amber-50 text-amber-700',
|
|
ROJO: 'bg-red-50 text-red-700',
|
|
GRIS: 'bg-gray-100 text-gray-600',
|
|
}
|
|
|
|
/**
|
|
* Semáforo del plan:
|
|
* - gris: LIQUIDADO o CANCELADO
|
|
* - rojo: tiene pagos VENCIDOS
|
|
* - ámbar: tiene un pago PARCIAL o el próximo vencimiento es en 7 días o menos
|
|
* - verde: al corriente
|
|
*/
|
|
export function semaforoPlan(plan: PlanPago, hoy: Date = new Date()): Semaforo {
|
|
if (statusPlan(plan) !== 'ACTIVO') return 'GRIS'
|
|
const estados = plan.pagos.map((p) => statusPago(p, hoy))
|
|
if (estados.includes('VENCIDO')) return 'ROJO'
|
|
if (estados.includes('PARCIAL')) return 'AMBAR'
|
|
const dias = diasParaProximoVencimiento(plan, hoy)
|
|
if (dias !== null && dias <= 7) return 'AMBAR'
|
|
return 'VERDE'
|
|
}
|
|
|
|
export default function SemaforoDot({ valor }: { valor: Semaforo }) {
|
|
return (
|
|
<span
|
|
className={`inline-block rounded-full px-2.5 py-0.5 text-xs font-medium ${CLASE_DOT[valor]}`}
|
|
>
|
|
{ETIQUETA[valor]}
|
|
</span>
|
|
)
|
|
}
|