# RF-FIN-004: Asientos Contables

## Identificacion

| Campo | Valor |
|-------|-------|
| **ID** | RF-FIN-004 |
| **Modulo** | MGN-010 Financial |
| **Titulo** | Asientos Contables |
| **Prioridad** | P0 - Critica |
| **Estado** | Draft |
| **Fecha** | 2025-12-05 |

---

## Descripcion

El sistema debe permitir registrar asientos contables (journal entries) con multiples lineas de debe y haber, validando que el asiento este cuadrado y afectando los saldos de las cuentas.

---

## Requisitos Funcionales

### RF-FIN-004.1: Estructura del Asiento

```typescript
interface JournalEntry {
  id: UUID;
  tenantId: UUID;
  periodId: UUID;           // Periodo contable
  entryNumber: string;      // "POL-2025-001234"
  entryDate: Date;          // Fecha contable
  description: string;      // Concepto/glosa
  reference?: string;       // Referencia externa
  sourceType?: string;      // "invoice", "payment", "manual"
  sourceId?: UUID;          // ID del documento origen
  status: EntryStatus;
  postedAt?: TIMESTAMPTZ;
  postedBy?: UUID;
  reversedEntryId?: UUID;   // Si es reverso
  createdBy: UUID;
  createdAt: TIMESTAMPTZ;
  updatedAt: TIMESTAMPTZ;
}

enum EntryStatus {
  DRAFT = 'draft',         // En edicion
  PENDING = 'pending',     // Pendiente aprobacion
  POSTED = 'posted',       // Contabilizado
  REVERSED = 'reversed'    // Reversado
}
```

### RF-FIN-004.2: Estructura de Linea

```typescript
interface JournalLine {
  id: UUID;
  entryId: UUID;
  lineNumber: number;
  accountId: UUID;          // Cuenta contable
  costCenterId?: UUID;      // Centro de costo
  description?: string;     // Detalle de linea
  debit: Decimal;           // Debe
  credit: Decimal;          // Haber
  currencyId: UUID;         // Moneda del movimiento
  exchangeRate: Decimal;    // Tipo de cambio
  debitBase: Decimal;       // Debe en moneda base
  creditBase: Decimal;      // Haber en moneda base
  partnerId?: UUID;         // Tercero (cliente/proveedor)
  tags?: string[];          // Etiquetas
  reconciled: boolean;      // Conciliado
  reconciledAt?: TIMESTAMPTZ;
}
```

### RF-FIN-004.3: Validaciones del Asiento

```typescript
interface EntryValidation {
  isValid: boolean;
  errors: ValidationError[];
  warnings: ValidationWarning[];
}

// Validaciones obligatorias
const VALIDATIONS = [
  'entry_balanced',        // Debe = Haber
  'date_in_open_period',   // Fecha en periodo abierto
  'accounts_are_detail',   // Cuentas de detalle
  'accounts_active',       // Cuentas activas
  'amounts_positive',      // Montos positivos
  'currency_valid',        // Moneda valida
  'exchange_rate_valid'    // Tipo de cambio valido
];

// Ejemplo de validacion
function validateEntry(entry: JournalEntry): EntryValidation {
  const totalDebit = entry.lines.reduce((sum, l) => sum + l.debitBase, 0);
  const totalCredit = entry.lines.reduce((sum, l) => sum + l.creditBase, 0);

  if (Math.abs(totalDebit - totalCredit) > 0.01) {
    return {
      isValid: false,
      errors: [{
        code: 'UNBALANCED',
        message: `Asiento descuadrado: Debe=${totalDebit}, Haber=${totalCredit}`
      }]
    };
  }
  // ... mas validaciones
}
```

### RF-FIN-004.4: Numeracion de Asientos

```typescript
interface EntryNumbering {
  tenantId: UUID;
  prefix: string;           // "POL", "ING", "EGR"
  yearFormat: string;       // "YYYY" o "YY"
  separator: string;        // "-"
  sequenceLength: number;   // 6 digitos
  resetYearly: boolean;     // Reiniciar cada año
  lastNumber: number;       // Ultimo numero usado
}

// Genera: POL-2025-000001
function generateEntryNumber(config: EntryNumbering, date: Date): string {
  const year = format(date, config.yearFormat);
  const sequence = (config.lastNumber + 1).toString().padStart(config.sequenceLength, '0');
  return `${config.prefix}${config.separator}${year}${config.separator}${sequence}`;
}
```

### RF-FIN-004.5: Mayorizar Asiento

Al contabilizar (post) un asiento:

1. Validar que este cuadrado
2. Validar fecha en periodo abierto
3. Actualizar saldos de cuentas
4. Marcar como posted
5. Registrar en audit log

```typescript
interface PostResult {
  entryId: UUID;
  entryNumber: string;
  postedAt: TIMESTAMPTZ;
  affectedAccounts: {
    accountId: UUID;
    previousBalance: Decimal;
    newBalance: Decimal;
  }[];
}
```

### RF-FIN-004.6: Reversar Asiento

```typescript
interface ReversalEntry {
  originalEntryId: UUID;
  reversalEntryId: UUID;
  reversalDate: Date;
  reason: string;
}

// El asiento de reverso:
// - Tiene las mismas lineas pero Debe/Haber invertidos
// - Referencia al asiento original
// - Anula efecto en saldos
```

### RF-FIN-004.7: Asientos desde Documentos

Los modulos de negocio generan asientos:

| Documento | Asiento |
|-----------|---------|
| Factura de Venta | Debe: CxC, Haber: Ingresos + IVA |
| Factura de Compra | Debe: Gastos + IVA, Haber: CxP |
| Pago Recibido | Debe: Banco, Haber: CxC |
| Pago Emitido | Debe: CxP, Haber: Banco |
| Ajuste Inventario | Debe/Haber: Inventario vs Ajustes |

---

## Operaciones

### Listar Asientos

```typescript
GET /api/v1/financial/journal?periodId=uuid&status=posted

Response:
{
  "data": [
    {
      "id": "uuid",
      "entryNumber": "POL-2025-001234",
      "entryDate": "2025-12-05",
      "description": "Pago a proveedor XYZ",
      "status": "posted",
      "totalDebit": 10000,
      "linesCount": 3
    }
  ]
}
```

### Crear Asiento

```typescript
POST /api/v1/financial/journal
{
  "entryDate": "2025-12-05",
  "description": "Registro de venta",
  "lines": [
    {
      "accountId": "uuid-cxc",
      "debit": 11600,
      "credit": 0,
      "description": "Cliente ABC"
    },
    {
      "accountId": "uuid-ingresos",
      "debit": 0,
      "credit": 10000,
      "description": "Venta de servicios"
    },
    {
      "accountId": "uuid-iva",
      "debit": 0,
      "credit": 1600,
      "description": "IVA 16%"
    }
  ]
}

Response:
{
  "id": "uuid",
  "entryNumber": "POL-2025-001235",
  "status": "draft",
  "totalDebit": 11600,
  "totalCredit": 11600,
  "isBalanced": true
}
```

### Contabilizar Asiento

```typescript
POST /api/v1/financial/journal/:id/post

Response:
{
  "id": "uuid",
  "status": "posted",
  "postedAt": "2025-12-05T10:00:00Z",
  "affectedAccounts": 3
}
```

### Reversar Asiento

```typescript
POST /api/v1/financial/journal/:id/reverse
{
  "reversalDate": "2025-12-06",
  "reason": "Error en monto"
}

Response:
{
  "originalEntryId": "uuid-original",
  "reversalEntryId": "uuid-reverso",
  "reversalNumber": "POL-2025-001236"
}
```

---

## Reglas de Negocio

| ID | Regla | Severidad |
|----|-------|-----------|
| BR-001 | Asiento debe estar cuadrado para contabilizar | Error |
| BR-002 | Fecha debe estar en periodo abierto | Error |
| BR-003 | Solo cuentas de detalle en asientos | Error |
| BR-004 | Asiento contabilizado no es editable | Error |
| BR-005 | Reverso de asiento ya reversado no permitido | Error |

---

## Criterios de Aceptacion

- [ ] CRUD de asientos contables
- [ ] Validacion automatica de cuadre
- [ ] Numeracion automatica configurable
- [ ] Mayorizar asiento (post)
- [ ] Reversar asiento
- [ ] Soporte multi-moneda
- [ ] Centros de costo opcionales
- [ ] Integracion con documentos

---

## Historial

| Version | Fecha | Autor | Cambios |
|---------|-------|-------|---------|
| 1.0 | 2025-12-05 | Requirements-Analyst | Creacion inicial |
