Skip to main content

Overview

The Amortization API manages payment schedules for down payments. When a sale is created with installment payments, the system automatically generates an amortization plan with individual cuotas (installments).

Model Structure

PlanAmortizacionVenta Model

Table: planes_amortizacion_venta
Primary Key: id_plan (auto-increment)

Attributes

id_plan
integer
Primary key, auto-increment
id_venta
integer
required
Foreign key to ventas table. The sale this plan belongs to.
tipo_plan
string
required
Plan type. Common values:
  • 'cuota_inicial': Down payment plan
  • 'credito': Credit/loan plan (future use)
valor_interes_anual
decimal(5,2)
Annual interest rate percentage. Currently 0 for down payments.
plazo_meses
integer
required
Total term in months
fecha_inicio
date
required
Plan start date (typically sale date)
observacion
text
Plan notes or description
created_at
timestamp
Record creation timestamp
updated_at
timestamp
Last update timestamp

Relationships

venta
BelongsTo
The sale this plan belongs to
cuotas
HasMany
Collection of PlanAmortizacionCuota records (individual installments)

PlanAmortizacionCuota Model

Table: planes_amortizacion_cuota
Primary Key: id_cuota (auto-increment)

Attributes

id_cuota
integer
Primary key, auto-increment
id_plan
integer
required
Foreign key to planes_amortizacion_venta table
numero_cuota
integer
required
Installment number (1, 2, 3, …)
fecha_vencimiento
date
required
Due date for this installment
valor_cuota
decimal(15,2)
required
Total installment amount
valor_interes
decimal(15,2)
Interest portion (currently 0 for down payments)
valor_capital
decimal(15,2)
required
Principal portion (equals valor_cuota for down payments)
saldo
decimal(15,2)
required
Remaining balance after this payment
estado
string
required
Payment status:
  • 'Pendiente': Unpaid
  • 'Pagada': Fully paid
  • 'Parcial': Partially paid
  • 'Vencida': Overdue
created_at
timestamp
Record creation timestamp
updated_at
timestamp
Last update timestamp

Relationships

planAmortizacion
BelongsTo
The parent payment plan
pagos
HasMany
Collection of Pago records applied to this cuota

Get Payment Plan for Sale

curl -X GET "https://api.coreprojects.com/ventas/{id_venta}/plan-amortizacion" \
  -H "Authorization: Bearer {token}"

Path Parameters

id_venta
integer
required
Sale ID

Response

{
  "id_plan": 1,
  "id_venta": 1,
  "tipo_plan": "cuota_inicial",
  "valor_interes_anual": 0.00,
  "plazo_meses": 18,
  "fecha_inicio": "2024-01-15",
  "observacion": "Plan cuota inicial (cada 1 mes(es))",
  "created_at": "2024-01-15T10:30:00.000000Z",
  "updated_at": "2024-01-15T10:30:00.000000Z",
  "cuotas": [
    {
      "id_cuota": 1,
      "id_plan": 1,
      "numero_cuota": 1,
      "fecha_vencimiento": "2024-01-15",
      "valor_cuota": 3750000.00,
      "valor_interes": 0.00,
      "valor_capital": 3750000.00,
      "saldo": 63750000.00,
      "estado": "Pagada"
    },
    {
      "id_cuota": 2,
      "numero_cuota": 2,
      "fecha_vencimiento": "2024-02-15",
      "valor_cuota": 3750000.00,
      "valor_interes": 0.00,
      "valor_capital": 3750000.00,
      "saldo": 60000000.00,
      "estado": "Pendiente"
    }
  ]
}

Generate Payment Plan (Internal)

Payment plans are automatically generated when creating or converting a sale. This section documents the internal logic.

Plan Generation Algorithm

protected function generarPlanCuotaInicial(Venta $venta, Proyecto $proyecto): void
{
    $plazo = (int)($venta->plazo_cuota_inicial_meses ?? 0);
    $monto = (float)($venta->cuota_inicial ?? 0);
    $frecuencia = (int)($venta->frecuencia_cuota_inicial_meses ?? 1);

    if ($plazo <= 0 || $monto <= 0) return;
    if ($frecuencia < 1) $frecuencia = 1;
    if ($frecuencia > $plazo) $frecuencia = $plazo;

    $fechaInicio = $venta->fecha_venta ?? now();

    // Calculate number of payments
    $numPagos = (int) ceil($plazo / $frecuencia);
    if ($numPagos < 1) $numPagos = 1;

    // Create plan
    $plan = PlanAmortizacionVenta::create([
        'id_venta' => $venta->id_venta,
        'tipo_plan' => 'cuota_inicial',
        'valor_interes_anual' => 0,
        'plazo_meses' => $plazo,
        'fecha_inicio' => $fechaInicio,
        'observacion' => "Plan cuota inicial (cada {$frecuencia} mes(es))",
    ]);

    // Calculate installment amounts
    $cuotaBase = (int) floor($monto / $numPagos);
    $residuo = $monto - ($cuotaBase * $numPagos);

    // Generate cuotas
    for ($i = 1; $i <= $numPagos; $i++) {
        $valorCuota = $cuotaBase;

        // Add remainder to last payment
        if ($i === $numPagos) {
            $valorCuota += $residuo;
        }

        $pagadoHastaAhora = ($cuotaBase * ($i - 1));
        $saldo = $monto - $pagadoHastaAhora - $valorCuota;
        $saldo = max($saldo, 0);

        $mesOffset = ($i - 1) * $frecuencia;

        PlanAmortizacionCuota::create([
            'id_plan' => $plan->id_plan,
            'numero_cuota' => $i,
            'fecha_vencimiento' => Carbon::parse($fechaInicio)->addMonths($mesOffset),
            'valor_cuota' => $valorCuota,
            'valor_interes' => 0,
            'valor_capital' => $valorCuota,
            'saldo' => $saldo,
            'estado' => 'Pendiente',
        ]);
    }
}

Example Calculation

Input:
  • Total down payment: $67,500,000
  • Term: 18 months
  • Frequency: 1 month (monthly payments)
Output:
  • Number of payments: 18
  • Base payment: $3,750,000
  • Last payment: $3,750,000 (includes any remainder)
Generated Cuotas:
CuotaDue DateAmountSaldoStatus
12024-01-153,750,00063,750,000Pendiente
22024-02-153,750,00060,000,000Pendiente
32024-03-153,750,00056,250,000Pendiente
……………
182024-06-153,750,0000Pendiente

Regenerate Payment Plan

curl -X POST "https://api.coreprojects.com/ventas/{id_venta}/regenerar-plan" \
  -H "Authorization: Bearer {token}"

Path Parameters

id_venta
integer
required
Sale ID
Regenerating a payment plan deletes all existing cuotas and creates new ones. This does NOT delete payment records, but breaks the link between payments and cuotas.

Use Cases

  • Sale terms were updated (different amount, term, or frequency)
  • Conversion from separation to sale
  • Correction of calculation errors

List Amortization Plans

curl -X GET "https://api.coreprojects.com/plan-amortizacion-venta" \
  -H "Authorization: Bearer {token}"

Query Parameters

id_proyecto
integer
Filter by project
documento_cliente
string
Filter by client document
tipo_plan
string
Filter by plan type (e.g., ‘cuota_inicial’)

Get Sales by Client for Plan Generation

curl -X GET "https://api.coreprojects.com/plan-amortizacion-venta/ventas-por-cliente?id_proyecto=1&documento_cliente=1234567890" \
  -H "Authorization: Bearer {token}"

Query Parameters

id_proyecto
integer
required
Project ID
documento_cliente
string
required
Client document

Export Payment Plan to PDF

curl -X POST "https://api.coreprojects.com/plan-amortizacion-venta/exportar" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "id_venta": 1
  }' \
  --output plan-amortizacion.pdf

Request Body

id_venta
integer
required
Sale ID to export payment plan for

Response

Returns PDF file with payment schedule table including:
  • Client and property information
  • Payment plan details (term, frequency)
  • Cuota table with due dates, amounts, and balances

Update Cuota Status

curl -X PATCH "https://api.coreprojects.com/planes-amortizacion-cuota/{id}/estado" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "estado": "Pagada"
  }'

Path Parameters

id
integer
required
Cuota ID

Request Body

estado
enum
required
New status: 'Pendiente', 'Pagada', 'Parcial', or 'Vencida'

Business Logic

Automatic Plan Generation

Payment plans are automatically generated when:
  1. Creating a Sale with tipo_operacion = 'venta'
  2. Converting a Separation to sale
  3. Updating a Sale that changes payment terms

Payment Frequency

The frecuencia_cuota_inicial_meses field controls payment intervals:
  • 1: Monthly payments
  • 2: Bimonthly (every 2 months)
  • 3: Quarterly (every 3 months)
  • 6: Semi-annual (every 6 months)
Example: 18-month term with frequency = 2 (bimonthly) → 9 payments

Interest-Free Down Payments

Current implementation:
  • valor_interes_anual = 0 (no interest charged)
  • valor_interes = 0 per cuota
  • valor_capital = valor_cuota (full payment to principal)
The model structure supports future interest calculations if business requirements change.

Cuota Status Management

Cuota status is typically updated when payments are recorded:
// After creating a payment
$pago = Pago::create([
    'id_venta' => $venta->id_venta,
    'id_cuota' => $cuota->id_cuota,
    'valor' => $amount,
]);

// Update cuota status
$totalPagado = Pago::where('id_cuota', $cuota->id_cuota)->sum('valor');

if ($totalPagado >= $cuota->valor_cuota) {
    $cuota->update(['estado' => 'Pagada']);
} elseif ($totalPagado > 0) {
    $cuota->update(['estado' => 'Parcial']);
} else {
    $cuota->update(['estado' => 'Pendiente']);
}

Overdue Detection

Implement a scheduled task to mark overdue cuotas:
public function marcarCuotasVencidas()
{
    PlanAmortizacionCuota::where('estado', 'Pendiente')
        ->whereDate('fecha_vencimiento', '<', now())
        ->update(['estado' => 'Vencida']);
}

Reporting Queries

Payment Schedule Summary

$resumen = [
    'total_cuotas' => $plan->cuotas->count(),
    'cuotas_pagadas' => $plan->cuotas->where('estado', 'Pagada')->count(),
    'cuotas_pendientes' => $plan->cuotas->where('estado', 'Pendiente')->count(),
    'cuotas_vencidas' => $plan->cuotas->where('estado', 'Vencida')->count(),
    'monto_pagado' => $plan->cuotas->where('estado', 'Pagada')->sum('valor_cuota'),
    'monto_pendiente' => $plan->cuotas->whereIn('estado', ['Pendiente', 'Vencida'])->sum('valor_cuota'),
    'proxima_cuota' => $plan->cuotas->where('estado', 'Pendiente')->sortBy('fecha_vencimiento')->first(),
];

Client Payment Schedule

$cuotasCliente = PlanAmortizacionCuota::whereHas('planAmortizacion.venta', function ($q) use ($documento) {
    $q->where('documento_cliente', $documento);
})
->with('planAmortizacion.venta')
->orderBy('fecha_vencimiento')
->get();

Project Payment Schedule

$cuotasProyecto = PlanAmortizacionCuota::whereHas('planAmortizacion.venta', function ($q) use ($idProyecto) {
    $q->where('id_proyecto', $idProyecto);
})
->whereBetween('fecha_vencimiento', [$fechaInicio, $fechaFin])
->orderBy('fecha_vencimiento')
->get();

Build docs developers (and LLMs) love