629 lines
13 KiB
Markdown
629 lines
13 KiB
Markdown
|
|
# Backend ERP – Perfil VeriFactu empresarial
|
|||
|
|
|
|||
|
|
## 1. Estado del documento
|
|||
|
|
|
|||
|
|
Estado: **IMPLEMENTADO PARCIALMENTE**
|
|||
|
|
|
|||
|
|
Este documento define el diseño backend del perfil VeriFactu de empresa dentro del módulo `customer-invoices`.
|
|||
|
|
|
|||
|
|
Decisiones consolidadas:
|
|||
|
|
|
|||
|
|
- El perfil VeriFactu pertenece al bounded context `customer-invoices`.
|
|||
|
|
- Es una configuración separada de `Company`.
|
|||
|
|
- Tiene scope de empresa mediante `companyId`.
|
|||
|
|
- El estado de configuración es explícito.
|
|||
|
|
- Se permite `obligated_without_verifactu` como estado real.
|
|||
|
|
- Los catálogos de clasificación y exención quedan modelados por código pero no se cierran todavía como enums.
|
|||
|
|
- `IssuedInvoice` deberá persistir dos snapshots distintos en una fase posterior:
|
|||
|
|
- clasificación fiscal efectiva;
|
|||
|
|
- política VeriFactu efectiva.
|
|||
|
|
- `VerifactuRecord` mantiene responsabilidad operativa y no absorbe el perfil empresarial.
|
|||
|
|
- El perfil no expone secretos ni passwords de certificados.
|
|||
|
|
|
|||
|
|
## 2. Propósito
|
|||
|
|
|
|||
|
|
Proporcionar una fuente de verdad empresarial para:
|
|||
|
|
|
|||
|
|
- aplicabilidad de la normativa;
|
|||
|
|
- exención y su motivo;
|
|||
|
|
- uso o no de modalidad VeriFactu;
|
|||
|
|
- fecha efectiva de activación;
|
|||
|
|
- renovación automática si aplica;
|
|||
|
|
- defaults de clasificación fiscal;
|
|||
|
|
- snapshot histórico al emitir una factura.
|
|||
|
|
|
|||
|
|
El diseño debe integrarse con el flujo actual:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
Proforma
|
|||
|
|
→ IssueProformaUseCase
|
|||
|
|
→ ProformaIssueReadModelAssembler
|
|||
|
|
→ proforma-to-issued-invoice-props-converter
|
|||
|
|
→ IssuedInvoice
|
|||
|
|
→ VerifactuRecord
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
sin trasladar esta resolución al frontend.
|
|||
|
|
|
|||
|
|
## 3. Ownership
|
|||
|
|
|
|||
|
|
El perfil debe vivir como agregado/configuración separado dentro de:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
modules/customer-invoices
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No debe vivir dentro de:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
identity
|
|||
|
|
companies
|
|||
|
|
Company aggregate
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Motivo:
|
|||
|
|
|
|||
|
|
- gobierna facturación;
|
|||
|
|
- afecta directamente a `IssuedInvoice`;
|
|||
|
|
- sus defaults deben materializarse al emitir;
|
|||
|
|
- identidad solo aporta `companyId`, company context y autorización.
|
|||
|
|
|
|||
|
|
## 4. Modelo de estados
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export const CompanyVerifactuProfileStatus = {
|
|||
|
|
NotConfigured: "not_configured",
|
|||
|
|
Exempt: "exempt",
|
|||
|
|
ObligatedWithoutVerifactu: "obligated_without_verifactu",
|
|||
|
|
ObligatedWithVerifactu: "obligated_with_verifactu",
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type CompanyVerifactuProfileStatus =
|
|||
|
|
(typeof CompanyVerifactuProfileStatus)[keyof typeof CompanyVerifactuProfileStatus];
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Semántica:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
not_configured
|
|||
|
|
→ la empresa aún no ha definido su perfil.
|
|||
|
|
|
|||
|
|
exempt
|
|||
|
|
→ la empresa queda configurada como exenta/no aplicable.
|
|||
|
|
|
|||
|
|
obligated_without_verifactu
|
|||
|
|
→ la empresa está obligada, pero no opera bajo modalidad VeriFactu.
|
|||
|
|
|
|||
|
|
obligated_with_verifactu
|
|||
|
|
→ la empresa está obligada y utiliza VeriFactu.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Se acepta `obligated_without_verifactu` como estado real del modelo.
|
|||
|
|
|
|||
|
|
## 5. Invariantes del estado
|
|||
|
|
|
|||
|
|
### `not_configured`
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
exemptionReasonCode = null
|
|||
|
|
exemptionReasonDetail = null
|
|||
|
|
verifactuEffectiveAt = null
|
|||
|
|
autoRenewMode = null
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No debe producir defaults efectivos de clasificación VeriFactu.
|
|||
|
|
|
|||
|
|
### `exempt`
|
|||
|
|
|
|||
|
|
Puede requerir:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
exemptionReasonCode
|
|||
|
|
exemptionReasonDetail
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No permite activación VeriFactu.
|
|||
|
|
|
|||
|
|
### `obligated_without_verifactu`
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
verifactuEffectiveAt = null
|
|||
|
|
autoRenewMode = null
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Puede tener defaults fiscales si son aplicables.
|
|||
|
|
|
|||
|
|
### `obligated_with_verifactu`
|
|||
|
|
|
|||
|
|
Requiere:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
verifactuEffectiveAt != null
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`autoRenewMode` solo tiene sentido aquí.
|
|||
|
|
|
|||
|
|
## 6. Obligación y exención
|
|||
|
|
|
|||
|
|
V1:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
exemptionReasonCode: string | null;
|
|||
|
|
exemptionReasonDetail: string | null;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No se cierra todavía un enum legal. Los códigos deberán validarse contra un catálogo backend cuando exista.
|
|||
|
|
|
|||
|
|
## 7. Activación VeriFactu
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export const AutoRenewMode = {
|
|||
|
|
Disabled: "disabled",
|
|||
|
|
Enabled: "enabled",
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type AutoRenewMode =
|
|||
|
|
(typeof AutoRenewMode)[keyof typeof AutoRenewMode];
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Campos:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
verifactuEffectiveAt: Date | null;
|
|||
|
|
autoRenewMode: AutoRenewMode | null;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No se modelan periodos históricos en V1.
|
|||
|
|
|
|||
|
|
## 8. Defaults empresariales
|
|||
|
|
|
|||
|
|
Solo defaults de clasificación fiscal:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export interface CompanyVerifactuDefaults {
|
|||
|
|
taxRegimeCode: string | null;
|
|||
|
|
operationQualificationCode: string | null;
|
|||
|
|
exemptionReasonCode: string | null;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No pertenecen:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
taxPercentage
|
|||
|
|
recPercentage
|
|||
|
|
retentionPercentage
|
|||
|
|
taxMode
|
|||
|
|
tax breakdown
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 9. Precedencia de defaults
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
document explicit
|
|||
|
|
→ customer
|
|||
|
|
→ company VeriFactu defaults
|
|||
|
|
→ backend fallback
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Backend resolverá la precedencia cuando la integración con emisión entre en alcance.
|
|||
|
|
|
|||
|
|
## 10. Clasificación fiscal vs cálculo fiscal
|
|||
|
|
|
|||
|
|
### Cálculo fiscal
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
taxPercentage
|
|||
|
|
recPercentage
|
|||
|
|
retentionPercentage
|
|||
|
|
taxable base
|
|||
|
|
totals
|
|||
|
|
tax breakdown
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Clasificación VeriFactu
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
regimeCode
|
|||
|
|
operationQualificationCode
|
|||
|
|
exemptionReasonCode
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No reutilizar `ProformaFiscalData` como sustituto de clasificación VeriFactu.
|
|||
|
|
|
|||
|
|
## 11. Snapshot de clasificación fiscal
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export interface IssuedInvoiceFiscalClassificationSnapshot {
|
|||
|
|
regimeCode: string | null;
|
|||
|
|
regimeLabel: string | null;
|
|||
|
|
operationQualificationCode: string | null;
|
|||
|
|
operationQualificationLabel: string | null;
|
|||
|
|
exemptionReasonCode: string | null;
|
|||
|
|
exemptionReasonLabel: string | null;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Queda diseñado, pero no implementado todavía en esta fase.
|
|||
|
|
|
|||
|
|
## 12. Snapshot de política VeriFactu
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export interface IssuedInvoiceVerifactuPolicySnapshot {
|
|||
|
|
profileStatus: CompanyVerifactuProfileStatus;
|
|||
|
|
usesVerifactu: boolean;
|
|||
|
|
effectiveAt: string | null;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Queda diseñado, pero no implementado todavía en esta fase.
|
|||
|
|
|
|||
|
|
## 13. Resolución durante la emisión
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
CompanyVerifactuProfile
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
ProformaIssueReadModelAssembler
|
|||
|
|
│
|
|||
|
|
├── carga proforma
|
|||
|
|
├── carga cliente
|
|||
|
|
├── carga perfil VeriFactu
|
|||
|
|
└── prepara contexto
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
proforma-to-issued-invoice-props-converter
|
|||
|
|
│
|
|||
|
|
├── resuelve precedencia
|
|||
|
|
├── construye FiscalClassificationSnapshot
|
|||
|
|
└── construye VerifactuPolicySnapshot
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
IssuedInvoice.create(...)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 14. Responsabilidad de `VerifactuRecord`
|
|||
|
|
|
|||
|
|
`IssuedInvoice`:
|
|||
|
|
- snapshot documental e histórico;
|
|||
|
|
- clasificación fiscal aplicada;
|
|||
|
|
- política VeriFactu aplicada.
|
|||
|
|
|
|||
|
|
`VerifactuRecord`:
|
|||
|
|
- estado operativo;
|
|||
|
|
- URL;
|
|||
|
|
- QR;
|
|||
|
|
- UUID;
|
|||
|
|
- tipo de operación.
|
|||
|
|
|
|||
|
|
No debe contener defaults empresariales ni obligación/exención.
|
|||
|
|
|
|||
|
|
## 15. Certificados
|
|||
|
|
|
|||
|
|
El perfil no contiene:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
password
|
|||
|
|
private key
|
|||
|
|
secret name
|
|||
|
|
certificate binary
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Solo podría admitir una referencia lógica no sensible si realmente fuera necesaria.
|
|||
|
|
|
|||
|
|
## 16. Boundary AEAT
|
|||
|
|
|
|||
|
|
Fuera de alcance:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
enqueue
|
|||
|
|
worker/jobs
|
|||
|
|
retries
|
|||
|
|
queries
|
|||
|
|
hash chaining
|
|||
|
|
cancelación
|
|||
|
|
rectificación
|
|||
|
|
payload AEAT
|
|||
|
|
outbox
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 17. API
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
GET /api/customer-invoices/settings/verifactu
|
|||
|
|
PUT /api/customer-invoices/settings/verifactu
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Ambos usan `requireIdentityTenant(params)` y los controladores resuelven `companyId` mediante `this.getTenantId()`.
|
|||
|
|
|
|||
|
|
## 18. GET
|
|||
|
|
|
|||
|
|
Si no existe fila:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
profile_status = "not_configured"
|
|||
|
|
configured = false
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Response conceptual:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export interface CompanyVerifactuProfileResponseDTO {
|
|||
|
|
profile_status: CompanyVerifactuProfileStatus;
|
|||
|
|
configured: boolean;
|
|||
|
|
uses_verifactu: boolean;
|
|||
|
|
exemption_reason_code: string | null;
|
|||
|
|
exemption_reason_detail: string | null;
|
|||
|
|
verifactu_effective_at: string | null;
|
|||
|
|
auto_renew_mode: AutoRenewMode | null;
|
|||
|
|
defaults: {
|
|||
|
|
tax_regime_code: string | null;
|
|||
|
|
operation_qualification_code: string | null;
|
|||
|
|
exemption_reason_code: string | null;
|
|||
|
|
};
|
|||
|
|
updated_at: string | null;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 19. PUT
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export interface UpdateCompanyVerifactuProfileRequestDTO {
|
|||
|
|
profile_status: CompanyVerifactuProfileStatus;
|
|||
|
|
exemption_reason_code?: string | null;
|
|||
|
|
exemption_reason_detail?: string | null;
|
|||
|
|
verifactu_effective_at?: string | null;
|
|||
|
|
auto_renew_mode?: AutoRenewMode | null;
|
|||
|
|
defaults: {
|
|||
|
|
tax_regime_code?: string | null;
|
|||
|
|
operation_qualification_code?: string | null;
|
|||
|
|
exemption_reason_code?: string | null;
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Debe ser idempotente para el mismo payload.
|
|||
|
|
|
|||
|
|
## 20. Errores
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
400 → combinación estructural inválida
|
|||
|
|
403 → falta de autorización
|
|||
|
|
409 → conflicto de concurrencia si se activa
|
|||
|
|
422 → validación de negocio si encaja con la convención del módulo
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No usar 404 por ausencia inicial de perfil.
|
|||
|
|
|
|||
|
|
## 21. Persistencia
|
|||
|
|
|
|||
|
|
Tabla conceptual:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
company_verifactu_profiles
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Campos:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
id
|
|||
|
|
company_id
|
|||
|
|
profile_status
|
|||
|
|
exemption_reason_code
|
|||
|
|
exemption_reason_detail
|
|||
|
|
verifactu_effective_at
|
|||
|
|
auto_renew_mode
|
|||
|
|
default_tax_regime_code
|
|||
|
|
default_operation_qualification_code
|
|||
|
|
default_exemption_reason_code
|
|||
|
|
created_at
|
|||
|
|
updated_at
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Restricciones:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
company_id NOT NULL
|
|||
|
|
profile_status NOT NULL
|
|||
|
|
UNIQUE(company_id)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Una fila activa por empresa.
|
|||
|
|
|
|||
|
|
DDL operativo vigente:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
docs/customer-invoices/sql/add-company-verifactu-profiles.sql
|
|||
|
|
docs/customer-invoices/sql/add-issued-invoice-verifactu-snapshots.sql
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 22. Concurrencia
|
|||
|
|
|
|||
|
|
Se difiere en V1.
|
|||
|
|
|
|||
|
|
La implementación actual no introduce `expected_updated_at` ni control optimista específico, porque el backend real no aporta hoy un patrón reutilizable de concurrency/versionado para este tipo de settings.
|
|||
|
|
|
|||
|
|
## 23. Rollout
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
sin fila
|
|||
|
|
→ not_configured
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Orden recomendado:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
1. ejecutar DDL de `company_verifactu_profiles`
|
|||
|
|
2. ejecutar DDL de snapshots VeriFactu en `issued_invoices`
|
|||
|
|
3. persistencia y GET seguro
|
|||
|
|
4. PUT/configuración
|
|||
|
|
5. UI Settings
|
|||
|
|
6. uso de defaults en emisión
|
|||
|
|
7. enforcement posterior
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
La ausencia de perfil no debe romper automáticamente la emisión actual durante transición.
|
|||
|
|
|
|||
|
|
## 24. Seguridad
|
|||
|
|
|
|||
|
|
Nunca aceptar `companyId` del frontend.
|
|||
|
|
|
|||
|
|
Implementado:
|
|||
|
|
|
|||
|
|
- autenticación;
|
|||
|
|
- company context;
|
|||
|
|
- aislamiento por `companyId`.
|
|||
|
|
|
|||
|
|
Permisos finos propuestos para una fase posterior:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
customer-invoices:verifactu-profile:read
|
|||
|
|
customer-invoices:verifactu-profile:update
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Aún no se han activado guards de permisos específicos, porque el módulo `customer-invoices` no tenía previamente una infraestructura de permiso fino conectada en estas rutas.
|
|||
|
|
|
|||
|
|
## 25. Auditoría
|
|||
|
|
|
|||
|
|
Deben ser auditables cambios en:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
profileStatus
|
|||
|
|
exemptionReasonCode
|
|||
|
|
verifactuEffectiveAt
|
|||
|
|
defaults
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No se ha implementado auditoría adicional en esta fase.
|
|||
|
|
|
|||
|
|
## 26. Modelo conceptual del agregado
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export interface CompanyVerifactuProfileProps {
|
|||
|
|
companyId: UniqueID;
|
|||
|
|
profileStatus: CompanyVerifactuProfileStatus;
|
|||
|
|
exemptionReasonCode: string | null;
|
|||
|
|
exemptionReasonDetail: string | null;
|
|||
|
|
verifactuEffectiveAt: Date | null;
|
|||
|
|
autoRenewMode: AutoRenewMode | null;
|
|||
|
|
defaults: CompanyVerifactuDefaults;
|
|||
|
|
createdAt: Date;
|
|||
|
|
updatedAt: Date;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export class CompanyVerifactuProfile extends AggregateRoot<CompanyVerifactuProfileProps> {
|
|||
|
|
public static create(
|
|||
|
|
props: CompanyVerifactuProfileProps,
|
|||
|
|
): Result<CompanyVerifactuProfile, Error>;
|
|||
|
|
|
|||
|
|
public reconfigure(
|
|||
|
|
props: CompanyVerifactuProfileReconfiguration,
|
|||
|
|
): Result<void, Error>;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Usar `import type` explícito en cabecera.
|
|||
|
|
|
|||
|
|
## 27. Catálogos pendientes
|
|||
|
|
|
|||
|
|
No se consolidan todavía enums para:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
operationQualificationCode
|
|||
|
|
exemptionReasonCode
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Se requiere una fase específica para definir:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
fuente normativa
|
|||
|
|
códigos
|
|||
|
|
labels
|
|||
|
|
compatibilidades
|
|||
|
|
versionado
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 28. Integración en `IssuedInvoice`
|
|||
|
|
|
|||
|
|
Implementado en Fase B:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
CompanyVerifactuProfile
|
|||
|
|
→ ProformaIssueReadModelAssembler
|
|||
|
|
→ resolución backend de clasificación/política
|
|||
|
|
→ ProformaToIssuedInvoiceConverter
|
|||
|
|
→ IssuedInvoice
|
|||
|
|
→ persistencia física en issued_invoices
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`IssuedInvoice` mantiene `taxRegime` como snapshot legacy existente y añade además:
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
fiscalClassification
|
|||
|
|
verifactuPolicy
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Decisiones reales:
|
|||
|
|
|
|||
|
|
- `taxRegimeCode` se resuelve con precedencia `document -> customer -> company -> sin valor`.
|
|||
|
|
- `operationQualificationCode` se resuelve hoy desde defaults empresariales o `null`.
|
|||
|
|
- `exemptionReasonCode` se resuelve hoy desde defaults empresariales o `null`.
|
|||
|
|
- `verifactuPolicy.profileStatus` se persiste como señal de snapshot V2.
|
|||
|
|
- facturas antiguas sin esa señal cargan con `fiscalClassification = null` y `verifactuPolicy = null`.
|
|||
|
|
- no se inventan labels de `operationQualification` ni `exemptionReason`; se exponen como `null` hasta disponer de catálogo backend real.
|
|||
|
|
|
|||
|
|
## 29. Tests implementados
|
|||
|
|
|
|||
|
|
Aggregate:
|
|||
|
|
- invariantes por cada estado;
|
|||
|
|
- combinaciones inválidas;
|
|||
|
|
- reconfigure.
|
|||
|
|
|
|||
|
|
Repository:
|
|||
|
|
- una fila por company;
|
|||
|
|
- aislamiento entre companies.
|
|||
|
|
|
|||
|
|
Use cases / controllers:
|
|||
|
|
- GET sin fila;
|
|||
|
|
- PUT válido/inválido;
|
|||
|
|
- autorización;
|
|||
|
|
|
|||
|
|
Fase B:
|
|||
|
|
- precedencia `document -> customer -> company` para `taxRegimeCode`;
|
|||
|
|
- fallback legacy sin perfil;
|
|||
|
|
- validación de `IssuedInvoiceVerifactuPolicy`.
|
|||
|
|
|
|||
|
|
## 30. Criterios de aceptación
|
|||
|
|
|
|||
|
|
1. Existe una configuración VeriFactu por empresa.
|
|||
|
|
2. Pertenece a `customer-invoices`.
|
|||
|
|
3. Los cuatro estados están explícitamente modelados.
|
|||
|
|
4. `obligated_without_verifactu` está soportado.
|
|||
|
|
5. Los defaults se resuelven en backend.
|
|||
|
|
6. Cálculo fiscal y clasificación VeriFactu están separados.
|
|||
|
|
7. `GET` devuelve `not_configured` si no existe fila.
|
|||
|
|
8. `PUT` crea o reconfigura el perfil de forma idempotente para el mismo payload.
|
|||
|
|
9. `VerifactuRecord` sigue siendo operativo.
|
|||
|
|
10. No se exponen secretos.
|
|||
|
|
11. Empresas sin perfil se leen como `not_configured`.
|
|||
|
|
12. `IssuedInvoice` persiste snapshots de clasificación/política sin reescribir facturas legacy.
|
|||
|
|
|
|||
|
|
## 31. Regla mental final
|
|||
|
|
|
|||
|
|
```txt
|
|||
|
|
Company
|
|||
|
|
→ identidad/contexto
|
|||
|
|
|
|||
|
|
CompanyVerifactuProfile
|
|||
|
|
→ política/defaults empresariales
|
|||
|
|
|
|||
|
|
IssuedInvoice
|
|||
|
|
→ snapshot histórico efectivo
|
|||
|
|
|
|||
|
|
VerifactuRecord
|
|||
|
|
→ estado operativo
|
|||
|
|
|
|||
|
|
AEAT integration
|
|||
|
|
→ lifecycle futuro
|
|||
|
|
```
|