FactuGES_ERP/docs/recurring-proformas/DOCUMENTO DE DISEÑO — recurring-proformas.md

3555 lines
47 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DOCUMENTO DE DISEÑO — `recurring-proformas`
## 1. Objetivo
Diseñar la característica **Proformas recurrentes** del ERP.
Una `RecurringProforma` representa una plantilla comercial programada que genera periódicamente nuevas `Proforma`.
La característica NO genera directamente `IssuedInvoice`.
Flujo obligatorio:
```text
RecurringProforma
↓
RecurringProformaOccurrence
↓
Proforma
↓ flujo normal existente
IssuedInvoice
```
La generación recurrente termina en la creación de una `Proforma`.
A partir de ese momento, la Proforma sigue exactamente el mismo ciclo de vida que una Proforma creada manualmente.
---
## 2. Alcance V1
V1 incluye:
- creación de plantillas recurrentes;
- edición;
- activación explícita;
- pausa y reanudación;
- finalización;
- archivado;
- calendario recurrente;
- generación automática;
- generación manual anticipada de la próxima ocurrencia;
- historial de ocurrencias;
- retry de ocurrencias fallidas;
- recuperación de ejecuciones abandonadas;
- creación desde una Proforma existente;
- duplicación de una recurrencia;
- herencia dinámica de determinadas configuraciones;
- idempotencia;
- locking transaccional;
- auditoría;
- frontend completo de gestión.
V1 NO incluye:
- emisión automática de `IssuedInvoice`;
- envío automático a VeriFactu;
- `Subscription`;
- contratos;
- trials;
- renovaciones;
- facturación por consumo;
- prorrateos;
- cobro automático;
- generación extraordinaria fuera de calendario;
- `skip` de ocurrencias;
- expresiones cron configurables;
- calendarios laborales;
- festivos;
- reglas avanzadas de períodos;
- variables dinámicas en textos;
- productos o artículos;
- permisos específicos de recurrencias;
- timezone por recurrencia;
- historial completo de intentos de ejecución.
## Columna Acciones
La columna `Acciones` de `recurring-proformas` reutiliza `RdxRowActions`, la
misma primitive visual del listado de Proformas. Se presenta como última
columna, alineada a la derecha, con trigger compacto, menú accesible y
separación visual para acciones destructivas.
La celda solo representa callbacks preparados por la página; no invoca APIs
directamente, no consume DTOs y no reconstruye reglas de dominio. El contenido
del menú depende del lifecycle de `RecurringProforma`, mientras que su
presentación e interacción siguen el estándar global del ERP.
---
## 3. Ubicación funcional
`RecurringProforma` pertenece al módulo:
```text
customer-invoices
```
como agregado independiente.
Estructura conceptual:
```text
customer-invoices
├─ proformas
├─ issued-invoices
└─ recurring-proformas
```
No se crea un módulo genérico:
```text
recurring-billing
```
porque actualmente solo existe un caso funcional real: generar Proformas recurrentes.
---
## 4. Principio fundamental
`RecurringProforma` no es una Proforma futura parcialmente creada.
Representa:
```text
configuración comercial futura
+
calendario
+
estado de automatización
```
Cada ocurrencia genera una `Proforma` completamente independiente.
Las Proformas previamente generadas nunca se modifican porque cambie posteriormente la `RecurringProforma`.
---
## 5. Agregados y objetos principales
El subdominio contiene:
```text
RecurringProforma
RecurringProformaItem
RecurringProformaItems
RecurrenceSchedule
RecurringProformaOccurrence
```
### Aggregate Roots
```text
RecurringProforma
RecurringProformaOccurrence
```
`RecurringProformaOccurrence` es Aggregate Root independiente.
No se carga como colección dentro de `RecurringProforma`.
Motivos:
- tiene identidad propia;
- tiene estados propios;
- tiene lifecycle propio;
- tiene retry;
- puede crecer indefinidamente;
- necesita consultas y paginación independientes;
- participa directamente en idempotencia y recuperación.
---
# 6. `RecurringProforma`
## 6.1 Identidad
Toda `RecurringProforma` tiene:
```text
id: UniqueID
```
`UniqueID` contiene un UUID, siguiendo la convención general de entidades del ERP.
El UUID es la identidad única técnica.
No existe numeración documental tipo:
```text
RP-0001
```
en V1.
---
## 6.2 Campos internos de identificación
```text
name
description
```
### `name`
Obligatorio.
Reglas:
```text
trimmed
no vacío
```
No es único.
Se permiten nombres duplicados dentro de una misma compañía.
### `description`
Opcional.
Uso exclusivamente interno.
No se copia a las Proformas generadas.
Debe diferenciarse de cualquier descripción comercial existente en `Proforma`.
---
## 6.3 Tenant
```text
companyId: UniqueID
```
Toda `RecurringProforma` pertenece a una compañía.
Es company-scoped.
---
## 6.4 Customer
```text
customerId?: UniqueID
```
En `draft` puede no existir.
En:
```text
active
paused
```
es obligatorio.
---
# 7. Estados de `RecurringProforma`
Estados V1:
```text
draft
active
paused
completed
archived
```
## `draft`
Representa una plantilla en preparación.
Puede estar incompleta.
No participa en el scheduler.
No genera occurrences.
Puede editarse libremente dentro de las invariantes mínimas del draft.
---
## `active`
Participa en el scheduler.
Debe encontrarse permanentemente en un estado válido para generar Proformas.
---
## `paused`
Suspende completamente la generación.
No permite:
- generación automática;
- `Generate next occurrence`.
El calendario no se modifica durante la pausa.
---
## `completed`
Recurrencia finalizada.
No genera más occurrences.
Es inmutable.
---
## `archived`
Estado histórico final.
Solo accesible desde `completed`.
Es inmutable.
No puede restaurarse en V1.
Para reutilizarla debe duplicarse.
---
# 8. Transiciones de estado
Permitidas:
```text
draft → active
draft → completed
active → paused
active → completed
paused → active
paused → completed
completed → archived
```
Prohibidas:
```text
completed → active
completed → paused
completed → draft
archived → *
draft → archived
active → archived
paused → archived
```
`completed` y `archived` son inmutables por `PATCH`.
---
# 9. Draft incompleto
`RecurringProforma.create()` debe aplicar solamente invariantes mínimas.
Un draft puede crearse únicamente con:
```text
name
```
Pueden faltar:
```text
customer
items
schedule
campos comerciales
paymentTerm
paymentMethod
language
currency
series
```
La barrera fuerte está en:
```text
draft → active
```
mediante activation readiness.
---
# 10. Contenido comercial
`RecurringProforma` debe replicar los campos comerciales no fiscales actualmente necesarios para crear una `Proforma`.
Antes de implementación debe auditarse el agregado y persistencia real de `Proforma`.
Solo se replicarán campos existentes y necesarios.
No se inventará un modelo comercial paralelo.
Se incluyen conceptualmente:
- descripción comercial;
- notas;
- observaciones;
- términos;
- textos comerciales;
- descuentos globales;
- líneas;
- condiciones de pago;
- forma de pago;
- idioma;
- moneda;
- series documentales;
- cualquier otro campo comercial no fiscal realmente usado por `Proforma`.
No se replican:
- impuestos materializados;
- tax breakdown;
- totales derivados;
- estado documental;
- referencia documental;
- fechas de Proforma;
- datos de emisión;
- VeriFactu.
---
# 11. `RecurringProformaItem`
Entidad independiente de `ProformaItem`.
No se reutiliza directamente `ProformaItem`.
Campos V1:
```text
id: UniqueID
description
quantity
unitPrice
discount
```
`discount` utiliza exactamente el mismo modelo funcional de descuento que el `ProformaItem` actual.
No contiene:
```text
taxes
taxDefinitionId
productId
articleId
```
Actualmente el ERP no dispone de productos ni artículos.
---
# 12. `RecurringProformaItems`
Colección basada en:
```text
Collection<RecurringProformaItem>
```
Actualización:
```text
items omitido
→ no modificar
items enviado
→ reemplazo completo
```
No se introduce actualización granular:
```text
create/update/delete
```
por línea en V1.
---
# 13. Items vacíos
En `draft`:
```text
items = []
```
es válido.
En:
```text
active
paused
```
no se puede guardar una colección vacía.
Activation readiness bloquea un draft vacío mediante:
```text
missing_items
```
---
# 14. Fiscalidad
`RecurringProforma` no almacena fiscalidad materializada.
Cada ocurrencia debe resolver nuevamente la fiscalidad vigente.
Flujo:
```text
RecurringProforma
↓
Customer actual
↓
contexto fiscal actual
↓
creación normal de Proforma
↓
impuestos
↓
totales
```
La recurrencia no implementa:
```text
RecurringProformaTaxCalculator
RecurringProformaTotalsCalculator
```
La lógica fiscal debe reutilizar la misma lógica utilizada por una Proforma creada manualmente.
Si la situación fiscal vigente impide construir una Proforma válida:
```text
Occurrence → failed
```
No se crea una Proforma parcial.
---
# 15. Recipient
En cada ocurrencia se obtiene nuevamente el Customer actual.
El recipient snapshot de la nueva Proforma debe construirse usando los datos actuales del Customer.
Cambios posteriores del Customer nunca modifican Proformas previamente generadas.
---
# 16. Cambio de Customer
Permitido en:
```text
draft
active
paused
```
No permitido en:
```text
completed
archived
```
En `active` y `paused`:
- el cambio afecta solo a futuras occurrences;
- las Proformas existentes permanecen intactas;
- se aplican las mismas reglas de compatibilidad que en cambio de Customer de Proforma;
- idioma o moneda explícitos se respetan;
- incompatibilidades reales bloquean el cambio;
- la fiscalidad futura se recalcula;
- el recipient futuro se obtiene del nuevo Customer.
`active` y `paused` nunca pueden quedar con:
```text
customerId = null
```
---
# 17. Customer inactivo
Si el Customer pasa a `inactive`:
```text
Occurrence → failed
```
La recurrencia no pasa automáticamente a `paused`.
La occurrence fallida bloquea las siguientes.
Opciones de resolución:
```text
reactivar Customer
→ retry
```
o:
```text
cambiar Customer
→ retry
```
---
# 18. Campos heredables
Los siguientes campos son opcionales:
```text
paymentTermId
paymentMethodId
language
currency
proformaSeriesId
issuedInvoiceSeriesId
```
Semántica:
```text
null
→ herencia dinámica
valor explícito
→ override autoritativo
```
Los valores heredados se resuelven en cada occurrence.
Nunca se escriben de vuelta en `RecurringProforma`.
Ejemplo:
```text
RecurringProforma.currency = null
Customer.currency = EUR
Occurrence
→ Proforma.currency = EUR
RecurringProforma.currency continúa = null
```
---
# 19. Overrides explícitos
Si el usuario selecciona manualmente un valor, aunque coincida con el valor heredado actual:
```text
→ pasa a ser override
```
Si selecciona nuevamente:
```text
Heredar
```
se persiste:
```text
null
```
---
# 20. PaymentMethod y PaymentTerm
Si existe override explícito y el recurso pasa a estar:
```text
inactive
unavailable
```
la occurrence falla.
No existe fallback automático.
El usuario debe:
```text
reactivar
```
o:
```text
cambiar el override
```
y posteriormente:
```text
retry
```
Si el valor es heredado, se utiliza la resolución normal vigente de creación de Proforma.
---
# 21. Idioma y moneda
También pueden heredarse dinámicamente.
Override explícito:
```text
→ autoritativo
```
Valor heredado:
```text
→ se resuelve nuevamente en cada occurrence
```
Cualquier incompatibilidad que impida generar una Proforma válida provoca:
```text
Occurrence → failed
```
---
# 22. Series documentales
`RecurringProforma` puede guardar opcionalmente:
```text
proformaSeriesId?: UniqueID
issuedInvoiceSeriesId?: UniqueID
```
La semántica debe replicar la de `Proforma`.
### Serie explícita
Si se configura explícitamente:
```text
→ debe utilizarse
```
Si deja de estar:
```text
activa
vigente
disponible
```
la occurrence falla.
No existe fallback silencioso.
### Serie heredada
Si es `null`:
```text
→ usar resolución/default vigente
```
### Issued invoice series
`issuedInvoiceSeriesId` solamente se copia como preferencia/configuración a la Proforma creada.
No implica emisión automática ni reserva de numeración.
---
# 23. Regla general explícito/heredado
```text
valor explícito
→ autoritativo
→ si deja de ser válido, failed
null
→ resolver dinámicamente
→ no persistir resultado en RecurringProforma
```
---
# 24. `RecurrenceSchedule`
Value Object inmutable.
Campos:
```text
startDate
endDate?
unit
interval
monthlyRule?
```
`unit`:
```text
week
month
year
```
`interval`:
```text
integer > 0
```
`monthlyRule`:
```text
same_day
last_day_of_month
```
---
# 25. Invariantes de `RecurrenceSchedule`
```text
startDate obligatorio
interval > 0
endDate == null
OR
endDate >= startDate
```
Para:
```text
unit = month
```
`monthlyRule` es obligatorio.
Para:
```text
unit = week
unit = year
```
`monthlyRule` debe ser `null`.
---
# 26. `startDate`
`startDate` representa siempre:
```text
la primera occurrence
+
el ancla del calendario
```
No existe:
```text
firstOccurrenceDate
```
separada.
---
# 27. Recurrencia semanal
Ejemplo:
```text
startDate = 07/09/2026
unit = week
interval = 2
```
Resultado:
```text
07/09
21/09
05/10
...
```
El día de la semana deriva de `startDate`.
---
# 28. Recurrencia mensual `same_day`
Ejemplo:
```text
startDate = 31/01/2027
unit = month
interval = 1
monthlyRule = same_day
```
Resultado:
```text
31/01
28/02
31/03
30/04
31/05
```
Si el día ancla no existe se usa el último día válido del mes.
El ancla original nunca se pierde.
---
# 29. Recurrencia mensual `last_day_of_month`
`startDate` sigue siendo siempre la primera occurrence exacta.
Ejemplo:
```text
startDate = 15/01/2027
interval = 1
monthlyRule = last_day_of_month
```
Resultado:
```text
15/01
28/02
31/03
30/04
31/05
```
Con:
```text
interval = 3
```
resultado:
```text
15/01
30/04
31/07
31/10
31/01
...
```
---
# 30. Recurrencia anual
El ancla se conserva desde `startDate`.
Caso:
```text
startDate = 29/02/2028
unit = year
interval = 1
```
Resultado:
```text
29/02/2028
28/02/2029
28/02/2030
28/02/2031
29/02/2032
```
Con `interval > 1` se aplica la misma regla sobre los años objetivo.
---
# 31. Cálculo del calendario
No calcular:
```text
next = previous occurrence + interval
```
si eso puede perder el ancla.
El cálculo debe derivarse siempre del calendario original basado en:
```text
startDate
+
índice lógico de occurrence
```
o una implementación matemáticamente equivalente que conserve el ancla.
---
# 32. `endDate`
Opcional.
Si es `null`:
```text
recurrencia indefinida
```
Es inclusiva:
```text
scheduledDate <= endDate
→ válida
```
`endDate` no genera por sí misma una occurrence.
Solo limita el calendario.
Ejemplo:
```text
startDate = 01/01
interval = 1 month
endDate = 15/03
```
Genera:
```text
01/01
01/02
01/03
```
No genera:
```text
15/03
```
---
# 33. Modificación de `endDate`
Puede:
- añadirse;
- adelantarse;
- retrasarse.
No puede quedar antes de una `scheduledDate` ya materializada como `generated`.
Si al adelantarla ya no quedan occurrences futuras y no existen incidencias:
```text
→ completed
```
---
# 34. Finalización automática
Al generar la última occurrence válida:
```text
next calculated date > endDate
```
entonces:
```text
nextOccurrenceDate = null
status = completed
```
si no existen incidencias pendientes.
---
# 35. EndDate pasada durante activación
Es válido activar una recurrencia cuyo rango completo esté en el pasado.
Si existen occurrences válidas dentro del rango:
```text
→ se procesan todas
```
Tras resolverlas correctamente:
```text
→ completed
```
Si una falla:
```text
→ permanece active
```
hasta resolverla.
---
# 36. `nextOccurrenceDate`
Estado interno derivado y persistido en `RecurringProforma`.
No pertenece a `RecurrenceSchedule`.
No es editable desde API ni frontend.
Semántica:
```text
draft
→ null
active
→ próxima occurrence lógica
paused
→ conserva el mismo valor
failed occurrence
→ continúa apuntando a esa scheduledDate
completed
→ null
archived
→ null
```
Al activar:
```text
nextOccurrenceDate = startDate
```
---
# 37. Hora y timezone
Las recurrencias trabajan con:
```text
DATE
```
no con hora configurable.
No existe timezone por `RecurringProforma`.
El scheduler determina `today` usando:
1. timezone de la compañía, si existe en el ERP;
2. en su defecto, política global del sistema.
Si cambia la timezone:
- no se modifican fechas del calendario;
- no se recalcula `nextOccurrenceDate`;
- solo cambia la evaluación de cuál es el día actual operativo.
---
# 38. Scheduler
La frecuencia exacta del scheduler es configuración de infraestructura.
No pertenece al dominio.
Debe ejecutarse periódicamente y procesar:
```text
status = active
AND nextOccurrenceDate <= today
```
Debe recuperar occurrences atrasadas.
No debe depender exclusivamente de:
```text
nextOccurrenceDate == today
```
---
# 39. Ocurrencias atrasadas
Si existen varias fechas vencidas:
```text
→ procesar secuencialmente
→ en orden cronológico
```
Ejemplo:
```text
enero
febrero
marzo
abril
```
Si marzo falla:
```text
enero → generated
febrero → generated
marzo → failed
abril → no procesada
```
---
# 40. Pausa
`pause` suspende la ejecución.
No modifica el calendario.
Ejemplo:
```text
nextOccurrenceDate = 01/10
pause
resume en diciembre
```
Después del resume:
```text
01/10
01/11
01/12
```
se procesan en orden.
Pausar no equivale a hacer `skip`.
---
# 41. `RecurringProformaOccurrence`
Aggregate Root independiente.
Campos V1:
```text
id: UniqueID
companyId: UniqueID
recurringProformaId: UniqueID
scheduledDate
status
attemptCount
lastAttemptAt?
processingStartedAt?
lastErrorCode?
lastErrorMessage?
proformaId?
generatedAt?
createdAt
updatedAt
```
No contiene snapshots comerciales ni fiscales.
---
# 42. Estados de occurrence
```text
pending
processing
failed
generated
```
### `pending`
Occurrence identificada pero no procesada.
### `processing`
Ejecución en curso.
### `failed`
Último intento falló.
Bloquea occurrences posteriores.
### `generated`
Proforma creada correctamente.
Estado terminal.
---
# 43. Transiciones de occurrence
Permitidas:
```text
pending → processing
processing → generated
processing → failed
failed → processing
```
`generated` no permite ninguna transición posterior.
---
# 44. Identidad lógica de occurrence
Debe existir:
```text
UNIQUE(recurring_proforma_id, scheduled_date)
```
Esto garantiza que una fecha lógica de una recurrencia solo tenga una occurrence.
---
# 45. Relación con Proforma
`RecurringProformaOccurrence` guarda:
```text
proformaId?: UniqueID
```
Debe existir:
```text
UNIQUE(proforma_id)
```
Una Proforma no puede pertenecer a dos occurrences.
La Proforma NO contiene:
```text
recurringProformaId
recurringOccurrenceId
```
La dirección de la relación es exclusivamente:
```text
Occurrence → Proforma
```
---
# 46. Proforma vinculada
En V1 una Proforma vinculada a una occurrence no puede eliminarse físicamente.
La FK debe proteger la trazabilidad.
No usar:
```text
ON DELETE SET NULL
ON DELETE CASCADE
```
sobre `proforma_id`.
---
# 47. Ciclo posterior de la Proforma
Una vez:
```text
Occurrence.status = generated
```
la occurrence no sigue el lifecycle de la Proforma.
La Proforma puede posteriormente:
- editarse;
- cambiar de estado;
- archivarse;
- emitirse.
La occurrence continúa:
```text
generated
```
---
# 48. IssuedInvoice en historial
`RecurringProformaOccurrence` NO guarda:
```text
issuedInvoiceId
```
El historial puede resolver como dato de lectura si la Proforma fue posteriormente emitida y qué `IssuedInvoice` resultó.
Es información derivada, no persistida en occurrence.
---
# 49. Creación lazy de occurrences
No se pre-generan occurrences futuras.
Flujo:
```text
RecurringProforma.nextOccurrenceDate
↓
scheduler / generate next
↓
crear o recuperar occurrence
↓
pending
↓
processing
```
---
# 50. `attemptCount`
Cada intento real incrementa:
```text
attemptCount += 1
```
y actualiza:
```text
lastAttemptAt
```
Incluye:
- primer intento;
- retry manual;
- recuperación de `processing` abandonado.
---
# 51. Fallos
Al fallar:
```text
status = failed
lastErrorCode = ...
lastErrorMessage = ...
```
No se guarda historial completo de intentos.
No existe:
```text
RecurringProformaOccurrenceAttempt
```
en V1.
Al generar finalmente correctamente:
```text
status = generated
lastErrorCode = null
lastErrorMessage = null
```
La auditoría conserva los eventos funcionales anteriores.
---
# 52. Fallo secuencial
Una occurrence `failed`:
- no pausa formalmente la `RecurringProforma`;
- bloquea occurrences posteriores;
- bloquea `complete`;
- requiere `retry`.
No existe `skip` en V1.
---
# 53. Retry
Solo aplicable a:
```text
status = failed
```
Utiliza la configuración actual de `RecurringProforma`.
No utiliza un snapshot de la configuración que existía durante el intento anterior.
Ejemplo:
```text
PaymentMethod A inactive
→ failed
usuario cambia a PaymentMethod B
retry
→ usa B
```
Se reutiliza la misma occurrence.
Nunca se crea otra para la misma `scheduledDate`.
---
# 54. `processing` abandonado
Una occurrence puede quedar en `processing` si el proceso cae.
Se utiliza:
```text
processingStartedAt
```
Si supera un threshold configurado en infraestructura:
```text
→ puede recuperarse
```
Debe reutilizarse la misma occurrence.
No introducir en dominio:
```text
workerId
heartbeat
leaseOwner
```
salvo necesidad técnica futura.
---
# 55. Generación manual
Existe en V1:
```text
Generate next occurrence
```
Solo disponible en:
```text
active
```
No disponible en:
```text
draft
paused
completed
archived
```
Procesa anticipadamente:
```text
nextOccurrenceDate
```
No crea una occurrence extraordinaria.
---
# 56. Generación anticipada
Ejemplo:
```text
nextOccurrenceDate = 01/10
usuario genera el 25/09
```
Resultado:
```text
Occurrence.scheduledDate = 01/10
Proforma.proformaDate = 25/09
Proforma.operationDate = 25/09
```
La siguiente fecha del calendario se calcula desde:
```text
scheduledDate / calendario original
```
no desde la fecha efectiva de generación.
---
# 57. Fechas de Proforma
En V1:
```text
proformaDate = generationDate
operationDate = generationDate
```
Después de generarse, el usuario puede modificar `operationDate` mediante el flujo normal de edición de Proforma.
No se implementan reglas como:
```text
último día del mes anterior
inicio del período
offset de N días
```
---
# 58. Generación anticipada fallida
Se comporta exactamente igual que una occurrence automática fallida:
```text
status = failed
nextOccurrenceDate no avanza
bloquea posteriores
requiere retry
```
---
# 59. Activación readiness
La transición:
```text
draft → active
```
requiere validación previa.
Endpoint:
```text
GET /recurring-proformas/:recurring_proforma_id/activation-readiness
```
Contrato conceptual:
```json
{
"can_activate": false,
"blockers": [
{
"code": "missing_items"
}
]
}
```
---
# 60. Activation blockers
Candidatos iniciales:
```text
missing_customer
customer_inactive
missing_items
missing_description
missing_schedule
invalid_schedule
missing_payment_method
payment_method_unavailable
payment_method_inactive
missing_payment_term
payment_term_unavailable
payment_term_inactive
invalid_currency
invalid_language
proforma_series_unavailable
issued_invoice_series_unavailable
invalid_fiscal_configuration
```
Los nombres definitivos deberán reutilizar los errores y códigos existentes cuando sea posible.
No duplicar semánticas ya existentes.
---
# 61. Readiness vs ejecución
Readiness solamente valida:
```text
¿puede activarse ahora?
```
Cada occurrence vuelve a resolver y validar los datos vivos.
Ejemplo:
```text
activation readiness = OK
↓
PaymentMethod se desactiva
↓
siguiente occurrence = failed
```
---
# 62. Edición por estado
## `draft`
Editable:
- name;
- description;
- Customer;
- items;
- campos comerciales;
- overrides;
- calendario;
- endDate.
Puede quedar incompleto.
## `active` / `paused`
Editable:
- name;
- description;
- Customer;
- items;
- campos comerciales;
- overrides;
- endDate.
Todo PATCH debe dejar la recurrencia en un estado generable.
No puede eliminar:
- Customer;
- todas las líneas;
- schedule.
## `completed` / `archived`
Completamente inmutables.
---
# 63. Edición del calendario
Mientras no exista ninguna occurrence:
```text
startDate
unit
interval
monthlyRule
```
pueden modificarse.
Una vez exista historial:
```text
startDate
unit
interval
monthlyRule
```
quedan bloqueados.
`endDate` sigue siendo modificable según sus reglas propias.
Para cambiar la cadencia se debe:
```text
complete
→ duplicate
→ configurar nuevo calendario
```
---
# 64. Estado resultante válido en active/paused
Cualquier `PATCH` sobre:
```text
active
paused
```
debe validar el estado completo resultante.
Ejemplo:
```text
paymentMethodId: null
```
solo se permite si la herencia resultante resuelve un método válido.
Misma regla para:
```text
paymentTermId
language
currency
proformaSeriesId
issuedInvoiceSeriesId
```
---
# 65. Finalización manual
Permitida desde:
```text
draft
active
paused
```
Una `draft` puede pasar directamente a `completed`.
Esto permite conservar una plantilla descartada sin borrarla.
No puede completarse mientras exista una occurrence:
```text
failed
```
---
# 66. Archivado
Solo:
```text
completed → archived
```
No restaurable en V1.
---
# 67. Borrado físico
Permitido únicamente cuando:
```text
status = draft
AND no existen occurrences
```
En cualquier otro caso:
```text
DELETE rechazado
```
---
# 68. Duplicación
Endpoint:
```text
POST /recurring-proformas/:recurring_proforma_id/duplicate
```
Resultado:
```text
nuevo UniqueID
status = draft
nextOccurrenceDate = null
sin occurrences
sin historial
```
Se copia configuración comercial y schedule.
No se copia estado operativo.
El nombre puede mantenerse igual porque no existe unicidad.
---
# 69. Crear desde Proforma
Endpoint:
```text
POST /recurring-proformas/from-proforma/:proforma_id
```
Crea:
```text
RecurringProforma draft
```
Copia:
- Customer;
- campos comerciales;
- items;
- descuentos;
- condiciones actuales;
- payment term;
- payment method;
- idioma;
- moneda;
- series.
No copia:
- ID;
- referencia;
- status;
- proformaDate;
- operationDate;
- impuestos;
- totales;
- issued invoice;
- VeriFactu.
---
# 70. Procedencia al crear desde Proforma
Si `Proforma` permite conocer si un campo era:
```text
inherited
explicit
```
debe conservarse esa procedencia.
Si no puede demostrarse:
```text
→ copiar el valor actual como override explícito
```
Nunca inferir una herencia que el modelo actual no pueda demostrar.
---
# 71. Persistencia
Tablas V1:
```text
recurring_proformas
recurring_proforma_items
recurring_proforma_occurrences
```
No se crean:
```text
recurring_proforma_attempts
recurring_proforma_history
recurrence_schedules
```
---
# 72. Tabla `recurring_proformas`
Campos conceptuales:
```text
id
company_id
name
description
status
customer_id
campos comerciales no fiscales
payment_term_id
payment_method_id
language_code
currency_code
proforma_series_id
issued_invoice_series_id
start_date
end_date
recurrence_unit
recurrence_interval
monthly_rule
next_occurrence_date
created_at
updated_at
```
Los campos comerciales definitivos deben salir de auditoría del modelo actual de `Proforma`.
---
# 73. Índices `recurring_proformas`
Inicialmente:
```text
INDEX (company_id, status, next_occurrence_date)
INDEX (company_id, customer_id)
```
No indexar `name` en V1 salvo evidencia real de necesidad.
---
# 74. Tabla `recurring_proforma_items`
Campos conceptuales:
```text
id
recurring_proforma_id
position
description
quantity_*
unit_price_*
discount_*
```
Sin:
```text
company_id
created_at
updated_at
```
`companyId` se deriva desde `recurring_proforma_id`, igual que en `proforma_items`.
---
# 75. Items FK
```text
recurring_proforma_id
→ recurring_proformas.id
ON DELETE CASCADE
```
Las líneas forman parte interna del agregado.
---
# 76. Orden de items
Debe conservarse orden comercial.
Usar:
```text
position
```
si ese es el patrón necesario tras auditar `ProformaItem`.
Recomendado:
```text
UNIQUE(recurring_proforma_id, position)
```
---
# 77. Tabla `recurring_proforma_occurrences`
Campos:
```text
id
company_id
recurring_proforma_id
scheduled_date
status
attempt_count
last_attempt_at
processing_started_at
last_error_code
last_error_message
proforma_id
generated_at
created_at
updated_at
```
---
# 78. `company_id` en occurrences
`RecurringProformaOccurrence` sí almacena explícitamente:
```text
company_id
```
Motivo:
- consultas tenant-scoped frecuentes;
- scheduler;
- incidencias;
- recuperación de processing;
- evitar JOINs o consultas previas.
Debe coincidir siempre con:
```text
RecurringProforma.companyId
```
No se utiliza FK compuesta.
La coherencia se garantiza explícitamente en dominio/repositorio.
---
# 79. Constraints occurrences
Obligatorios:
```text
UNIQUE(recurring_proforma_id, scheduled_date)
UNIQUE(proforma_id)
```
FKs:
```text
recurring_proforma_id
→ recurring_proformas.id
ON DELETE RESTRICT
```
```text
proforma_id
→ proformas.id
ON DELETE RESTRICT
```
---
# 80. Índices occurrences
Inicialmente:
```text
INDEX (company_id, status)
INDEX (company_id, status, processing_started_at)
UNIQUE (recurring_proforma_id, scheduled_date)
UNIQUE (proforma_id)
```
No añadir índices adicionales sin necesidad demostrada.
---
# 81. `hasIssue`
No se persiste.
Se deriva:
```text
exists occurrence
where status = failed
```
Puede exponerse en list/detail:
```text
has_issue: boolean
```
---
# 82. Datos derivados no persistidos inicialmente
No guardar:
```text
last_occurrence_date
last_generated_at
occurrence_count
has_issue
```
Se materializarán únicamente si en el futuro existe evidencia de problemas de rendimiento.
---
# 83. Repositorios
Contratos separados:
```text
IRecurringProformaRepository
IRecurringProformaOccurrenceRepository
```
No usar repositorio genérico.
---
# 84. Finders
Separar consultas especializadas:
```text
RecurringProformaFinder
RecurringProformaOccurrenceFinder
RecurringProformaExecutionFinder
```
Sin CQRS formal.
`ExecutionFinder` puede resolver:
- recurrencias vencidas;
- incidencias;
- stale processing;
- consultas operativas del scheduler.
---
# 85. Application services
Servicios candidatos V1:
```text
RecurringProformaCreator
RecurringProformaUpdater
RecurringProformaDeleter
RecurringProformaStatusChanger
RecurringProformaReadinessChecker
RecurringProformaDuplicator
RecurringProformaFromProformaCreator
RecurringProformaMaterializer
RecurringProformaOccurrenceProcessor
RecurringProformaOccurrenceRetryService
RecurringProformaSchedulerProcessor
```
Evitar:
```text
RecurringProformaManager
RecurringBillingEngine
GenericRecurringService
GenericSchedulerService
```
---
# 86. `RecurringProformaMaterializer`
Responsabilidad:
```text
RecurringProforma actual
+
Customer actual
+
defaults actuales
+
catálogos actuales
+
contexto fiscal actual
↓
entrada válida para crear Proforma
```
No persiste.
No implementa lógica fiscal propia.
---
# 87. Reutilización de `ProformaCreator`
La generación recurrente debe acabar utilizando el mismo servicio responsable de crear Proformas normales.
No debe existir una segunda implementación de creación de Proforma específica para recurrencias.
La recurrencia materializa la entrada.
`ProformaCreator` aplica:
- invariantes;
- creación;
- fiscalidad;
- cálculos;
- persistencia mediante el flujo habitual.
---
# 88. Transacción de generación
La generación exitosa debe ser atómica.
Conceptualmente:
```text
BEGIN
lock RecurringProforma
crear/recuperar occurrence
mark processing
resolver configuración vigente
crear Proforma
persistir Proforma
occurrence.markGenerated(proformaId)
avanzar nextOccurrenceDate
o completar RecurringProforma
persistir RecurringProforma
persistir occurrence
auditoría
COMMIT
```
Si cualquier paso falla:
```text
ROLLBACK
```
No debe quedar:
- Proforma huérfana;
- occurrence generated parcial;
- `nextOccurrenceDate` avanzado incorrectamente.
---
# 89. Persistencia del fallo
Tras rollback de la generación, el estado:
```text
failed
```
se registra en una transacción separada.
Debe conservar:
```text
attemptCount
lastAttemptAt
lastErrorCode
lastErrorMessage
```
---
# 90. Locking
V1 usa locking pesimista/transaccional por `RecurringProforma`.
Objetivo:
```text
solo una ejecución puede avanzar simultáneamente
nextOccurrenceDate
```
Segunda barrera defensiva:
```text
UNIQUE(recurring_proforma_id, scheduled_date)
```
No introducir distributed lock externo salvo necesidad real del despliegue.
---
# 91. Scheduler
Infrastructure contiene un worker periódico.
Debe limitarse a invocar Application.
Conceptualmente:
```text
Infrastructure scheduler
↓
ProcessDueRecurringProformasUseCase
```
No contiene:
- cálculo de calendario;
- creación de Proforma;
- lógica fiscal;
- transiciones de dominio.
---
# 92. Procesamiento por recurrencia
Para cada recurrencia:
```text
procesar occurrences vencidas
en orden cronológico
de forma secuencial
```
Recurrencias distintas pueden procesarse independientemente.
Un fallo en una recurrencia no impide procesar las demás.
---
# 93. Recovery
Puede existir:
```text
RecoverStaleRecurringProformaOccurrencesUseCase
```
o integrarse de forma limpia en el worker principal.
La lógica debe permanecer en Application.
---
# 94. Auditoría
Eventos funcionales V1:
```text
recurring_proforma.created
recurring_proforma.updated
recurring_proforma.activated
recurring_proforma.paused
recurring_proforma.resumed
recurring_proforma.completed
recurring_proforma.archived
recurring_proforma.deleted
recurring_proforma_occurrence.generated
recurring_proforma_occurrence.failed
recurring_proforma_occurrence.retried
```
No auditar eventos puramente técnicos:
```text
pending → processing
lock acquired
lease recovered
```
---
# 95. Estado auditable de occurrence
Debe incluir al menos:
```text
scheduledDate
status
attemptCount
proformaId
lastErrorCode
```
`generated` debe permitir identificar la Proforma creada.
---
# 96. Permisos
V1 no introduce permisos específicos de recurrencias.
Se reutiliza la autorización existente del módulo `customer-invoices`.
El worker ejecuta como sistema.
La auditoría debe distinguir:
```text
usuario
sistema
```
---
# 97. API HTTP
Base:
```text
/recurring-proformas
```
Todas las operaciones de creación relacionadas con recurrencias quedan bajo este recurso.
---
# 98. CRUD
```text
GET /recurring-proformas
POST /recurring-proformas
GET /recurring-proformas/:recurring_proforma_id
PATCH /recurring-proformas/:recurring_proforma_id
DELETE /recurring-proformas/:recurring_proforma_id
```
---
# 99. Readiness y lifecycle
```text
GET /recurring-proformas/:recurring_proforma_id/activation-readiness
PATCH /recurring-proformas/:recurring_proforma_id/activate
PATCH /recurring-proformas/:recurring_proforma_id/pause
PATCH /recurring-proformas/:recurring_proforma_id/resume
PATCH /recurring-proformas/:recurring_proforma_id/complete
PATCH /recurring-proformas/:recurring_proforma_id/archive
```
`status` no es editable mediante PATCH general.
---
# 100. Ocurrencias
```text
GET /recurring-proformas/:recurring_proforma_id/occurrences
GET /recurring-proformas/:recurring_proforma_id/occurrences/:occurrence_id
```
---
# 101. Acciones manuales
```text
POST /recurring-proformas/:recurring_proforma_id/generate-next-occurrence
POST /recurring-proformas/:recurring_proforma_id/occurrences/:occurrence_id/retry
```
---
# 102. Creación desde Proforma
```text
POST /recurring-proformas/from-proforma/:proforma_id
```
---
# 103. Duplicación
```text
POST /recurring-proformas/:recurring_proforma_id/duplicate
```
---
# 104. DTO CREATE
`POST /recurring-proformas` puede contener solamente:
```json
{
"name": "Mantenimiento mensual"
}
```
El backend crea:
```text
status = draft
```
No acepta:
```text
id
company_id
status
next_occurrence_date
created_at
updated_at
```
---
# 105. Semántica PATCH
Regla general:
```text
omitido
→ no modificar
null
→ ausencia explícita / quitar override cuando aplique
valor
→ configurar explícitamente
```
Ejemplo:
```json
{
"payment_method_id": null
}
```
significa:
```text
eliminar override
→ volver a herencia
```
---
# 106. PATCH collections
```text
items omitido
→ no tocar
items enviado
→ reemplazo completo
```
No introducir actualización granular de items.
---
# 107. DTO Schedule
Shape conceptual:
```json
{
"start_date": "2026-09-01",
"end_date": null,
"unit": "month",
"interval": 1,
"monthly_rule": "same_day"
}
```
En PATCH el objeto puede modificarse parcialmente.
Ejemplo:
```json
{
"schedule": {
"end_date": "2027-12-31"
}
}
```
Application reconstruye y valida el `RecurrenceSchedule` completo.
---
# 108. DTOs exactos
Todo valor exacto debe seguir las convenciones existentes del ERP:
```text
Money
Amount
Percentage
Quantity
```
No deben viajar como `number` cuando requieran exactitud decimal.
Los schemas Zod son SSOT.
Los snapshot builders deben tiparse contra los DTO inferidos.
---
# 109. Responses heredables
El frontend debe conocer:
```text
valor configurado
valor actualmente resuelto
```
Ejemplo conceptual:
```json
{
"payment_method": {
"configured_id": null,
"resolved": {
"id": "...",
"name": "Transferencia"
}
}
}
```
No es necesario un campo redundante:
```text
source = inherited
```
porque puede derivarse de `configured_id`.
---
# 110. Listado HTTP
Filtros V1:
```text
search
status
customer_id
has_issue
next_occurrence_date_from
next_occurrence_date_to
page
per_page
sort
```
Búsqueda rápida al menos por:
```text
name
description
```
y por Customer si el Finder actual permite hacerlo de forma consistente con otros listados.
---
# 111. Ordenación
Campos útiles:
```text
name
status
next_occurrence_date
created_at
updated_at
```
La lista debe seguir las mismas convenciones de paginación y sorting ya utilizadas en el ERP.
---
# 112. Frontend
Nombre visible:
```text
Proformas recurrentes
```
No usar:
```text
Facturación recurrente
```
porque V1 no emite facturas automáticamente.
---
# 113. Navegación
Dentro de facturación de clientes:
```text
Facturación de clientes
├─ Proformas
├─ Proformas archivadas
├─ Proformas recurrentes
├─ Proformas recurrentes archivadas
└─ Facturas emitidas
```
---
# 114. Rutas frontend
Conceptualmente:
```text
/recurring-proformas
/recurring-proformas/create
/recurring-proformas/:recurring_proforma_id
/recurring-proformas/:recurring_proforma_id/update
/recurring-proformas/archived
```
---
# 115. Listado frontend
Debe reutilizar el mismo patrón visual que el listado actual de Proformas:
- filtros;
- tabla;
- sorting;
- paginación;
- búsqueda;
- acciones;
- loading;
- errores;
- componentes Rdx existentes.
Columnas recomendadas:
```text
Nombre
Cliente
Estado
Frecuencia
Próxima generación
Incidencias
Última actualización
Acciones
```
---
# 116. Filtro segmentado
Utilizar un control equivalente a:
```text
ProformaStatusSegmentedFilter
```
Estados operativos:
```text
Todas
Borrador
Activas
Pausadas
Completadas
```
`archived` no aparece ahí.
Las archivadas tienen entrada propia en menú lateral.
---
# 117. Archivadas
Ruta:
```text
/recurring-proformas/archived
```
Debe reutilizar exactamente el mismo listado y tabla con filtro fijo:
```text
status = archived
```
---
# 118. Incidencias
Una incidencia NO es estado de `RecurringProforma`.
Visualmente:
```text
Activa + Incidencia
```
`hasIssue` deriva de una occurrence `failed`.
Debe existir filtro:
```text
Todas
Con incidencias
Sin incidencias
```
---
# 119. Editor
Secciones recomendadas:
```text
Identificación interna
Cliente
Contenido comercial
Condiciones heredables
Calendario
```
---
# 120. Identificación interna UI
Campos:
```text
Nombre
Descripción interna
```
Debe indicarse que:
```text
description
```
es exclusivamente interna y no se copia a Proformas.
---
# 121. UI de herencia
Debe distinguir claramente:
```text
Heredar
vs
override explícito
```
Ejemplo:
```text
Forma de pago
[ Heredar del cliente ] Transferencia actual
```
No mostrar simplemente el valor resuelto como si estuviera fijado.
---
# 122. Preview de calendario
El editor debe mostrar aproximadamente las próximas 3–5 fechas calculadas.
Ejemplo:
```text
Próximas fechas
01/09/2026
01/10/2026
01/11/2026
01/12/2026
```
La lógica debe coincidir exactamente con `RecurrenceSchedule`.
---
# 123. Activación UI
Antes de activar:
```text
GET activation-readiness
```
Si existen blockers:
```text
→ mostrar lista funcional traducida
```
El backend vuelve a validar igualmente durante la transición.
---
# 124. Historial UI
Tabla de occurrences:
```text
Fecha programada
Estado
Proforma
Factura emitida
Intentos
Último intento
Acciones
```
---
# 125. Estados occurrence en UI
```text
pending → Pendiente
processing → Procesando
failed → Fallida
generated → Generada
```
`processing` es solo informativo.
No tiene acciones manuales.
---
# 126. Errores UI
`lastErrorCode` es la fuente para mensajes funcionales traducidos.
`lastErrorMessage`:
- no se muestra crudo en tabla;
- puede utilizarse en detalle técnico/soporte cuando aporte valor.
---
# 127. Generate next UI
Solo disponible en `active`.
Si la fecha futura se genera anticipadamente, el modal debe explicar:
- cuál era la fecha programada;
- que esa occurrence será consumida;
- que la Proforma usará la fecha actual de generación.
---
# 128. Retry UI
Solo visible para:
```text
Occurrence.status = failed
```
Debe indicar que:
```text
el retry utiliza la configuración actual de la recurrencia
```
No existe acción:
```text
Skip
```
---
# 129. Pause UI
Debe advertir:
```text
la pausa no desplaza ni elimina fechas
```
Las occurrences vencidas durante la pausa se recuperarán al reanudar.
---
# 130. Resume UI
Si hay occurrences vencidas, puede informar de que serán procesadas al reanudar.
---
# 131. Completed / Archived UI
Ambos read-only.
Acciones:
```text
completed
→ Duplicate
→ Archive
archived
→ Duplicate
```
---
# 132. Matriz de acciones UI
| Estado | Editar | Activar | Pausar | Reanudar | Generar próxima | Completar | Archivar | Duplicar |
|---|---:|---:|---:|---:|---:|---:|---:|---:|
| draft | Sí | Sí | No | No | No | Sí | No | Sí |
| active | Sí | No | Sí | No | Sí | Sí* | No | Sí |
| paused | Sí | No | No | Sí | No | Sí* | No | Sí |
| completed | No | No | No | No | No | No | Sí | Sí |
| archived | No | No | No | No | No | No | No | Sí |
`*` No disponible si existe occurrence `failed`.
---
# 133. Testing obligatorio
Debe cubrirse específicamente:
## Calendario
- week;
- month;
- year;
- interval > 1;
- día 31;
- 29 febrero;
- last day;
- endDate inclusiva;
- endDate entre occurrences;
- conservación del ancla.
## Estados
Todas las transiciones válidas e inválidas.
## Draft
Creación únicamente con `name`.
## Herencia
Para todos los campos heredables:
- inherited;
- explicit;
- cambio posterior de defaults;
- recurso explícito inactivo;
- eliminación de override;
- no materialización del valor heredado en la recurrencia.
## Customer
- inactive;
- cambio de Customer;
- compatibilidad;
- efecto solo futuro.
## Generación
- success;
- fiscalidad actual;
- recipient actual;
- descuentos;
- series;
- fechas;
- nextOccurrenceDate.
## Idempotencia
Ejecutar dos veces:
```text
(recurringProformaId, scheduledDate)
```
debe producir:
```text
1 occurrence
1 Proforma
```
## Concurrencia
Dos workers sobre la misma recurrencia:
```text
→ exactamente una generación efectiva
```
## Atomicidad
Si falla la generación:
- no queda Proforma;
- no avanza calendario;
- occurrence termina `failed`.
## Retry
- usa configuración actual;
- misma occurrence;
- incrementa intentos;
- no duplica Proforma.
## Recovery
`processing` abandonado debe recuperarse sin duplicados.
## Pause/resume
Debe recuperar fechas vencidas sin desplazar calendario.
## Generate next
Debe diferenciar:
```text
scheduledDate
generationDate
```
## EndDate
Debe completar automáticamente al agotarse el calendario.
## Delete
Solo:
```text
draft + sin occurrences
```
## From Proforma
Debe copiar únicamente configuración comercial válida y respetar procedencia cuando sea demostrable.
## Duplicate
Debe crear:
```text
nuevo UUID
draft
sin occurrences
sin nextOccurrenceDate
```
---
# 134. Criterio E2E principal de aceptación
V1 debe demostrar correctamente este escenario:
```text
1. Crear RecurringProforma draft.
2. Guardarla inicialmente solo con name.
3. Configurar Customer, líneas y calendario mensual.
4. Configurar campos heredados y overrides.
5. Ejecutar activation-readiness.
6. Activar.
7. Generar primera Proforma automáticamente.
8. Comprobar recipient actual.
9. Comprobar campos comerciales.
10. Comprobar fiscalidad e impuestos actuales.
11. Comprobar totales.
12. Desactivar un PaymentMethod explícito.
13. Ejecutar siguiente occurrence.
14. La occurrence falla.
15. No se crea Proforma.
16. No se procesa la occurrence posterior.
17. Cambiar PaymentMethod.
18. Retry.
19. Se genera exactamente una Proforma para esa scheduledDate.
20. El calendario continúa.
21. Pausar.
22. Dejar vencer varias fechas.
23. Reanudar.
24. Procesar las fechas atrasadas en orden.
25. Alcanzar endDate.
26. Completar automáticamente.
27. Archivar.
28. Consultar historial.
29. Navegar a las Proformas generadas.
30. Resolver de forma derivada las IssuedInvoices resultantes.
```
---
# 135. Decisiones anti-overengineering
No introducir en V1:
```text
Subscription
Contract
RecurringBillingEngine
GenericScheduler
GenericRecurrence
Domain Event Bus
CQRS formal
OccurrenceAttempt
snapshot comercial por occurrence
snapshot fiscal por occurrence
cron configurable
timezone por recurrencia
skip
cancel occurrence
generación extraordinaria
auto issue
auto VeriFactu
products
articles
price lists
period expressions
template variables
business day calendars
holiday calendars
```
Preferencia permanente:
```text
claridad semántica
>
abstracción anticipada
```
---
# 136. Regla arquitectónica final
La separación funcional debe mantenerse:
```text
RecurringProforma
→ define intención comercial futura y calendario
RecurringProformaOccurrence
→ registra ejecución de una fecha lógica
Proforma
→ representa el documento comercial generado
IssuedInvoice
→ representa el snapshot fiscal definitivo tras emisión
```
Ningún agregado debe asumir responsabilidades pertenecientes a otro.
En particular:
```text
RecurringProforma
```
no calcula fiscalidad ni emite facturas.
```text
RecurringProformaOccurrence
```
no sigue el lifecycle posterior de la Proforma.
```text
Proforma
```
no conoce que fue creada por una recurrencia.
La colaboración entre estos conceptos se orquesta desde Application.
---
# 137. Regla final de V1
`recurring-proformas` debe proporcionar una automatización:
```text
explícita
auditable
idempotente
recuperable
tenant-scoped
fiscalmente segura
sin emisión automática
```
La característica debe priorizar:
```text
generar correctamente una única Proforma
para cada occurrence lógica
```
por encima de cualquier automatización adicional.
La evolución posterior hacia emisión automática, períodos avanzados, variables dinámicas o conceptos de suscripción deberá diseñarse como extensión explícita y no condicionar innecesariamente V1.