@@ -33,14 +33,28 @@ type Row = Record<string, any>;
3333/** TTL for the nav-badge counts: high-frequency polls collapse to one query per window. */
3434const BADGE_TTL_MS = 15_000 ;
3535
36- /** Send order per document type. */
36+ /**
37+ * Send order per document type. Anulación is the reverse of creation (LIFO):
38+ * you can only "pop" the top of the stack, so the anular_* orders run after the
39+ * forward flow and in reverse (cumplido manifiesto → cumplido remesa →
40+ * manifiesto → remesa). The `dependenciaPendiente()` check enforces this.
41+ *
42+ * anular_cumplido_inicial_remesa (proc 54) has no fixed slot: it is injected
43+ * reactively at runtime with an orden just below the step that the RNDC says
44+ * needs it — see the remediation logic in Phase 2.
45+ */
3746const ORDEN : Record < string , number > = {
3847 tercero : 10 ,
3948 vehiculo : 20 ,
4049 remesa : 30 ,
4150 manifiesto : 40 ,
4251 cumplido_remesa : 50 ,
4352 cumplido_manifiesto : 60 ,
53+ anular_cumplido_manifiesto : 70 ,
54+ anular_cumplido_remesa : 80 ,
55+ anular_cumplido_inicial_remesa : 85 , // nominal; real orden set on reactive injection
56+ anular_manifiesto : 90 ,
57+ anular_remesa : 100 ,
4458} ;
4559/** RNDC proceso per document type. */
4660const PROCESO : Record < string , number > = {
@@ -50,6 +64,11 @@ const PROCESO: Record<string, number> = {
5064 manifiesto : 4 ,
5165 cumplido_remesa : 5 ,
5266 cumplido_manifiesto : 6 ,
67+ anular_cumplido_manifiesto : 29 ,
68+ anular_cumplido_remesa : 28 ,
69+ anular_cumplido_inicial_remesa : 54 ,
70+ anular_manifiesto : 32 ,
71+ anular_remesa : 9 ,
5372} ;
5473
5574async function fila ( exec : Queryable , sql : string , params : unknown [ ] ) : Promise < Row | null > {
@@ -622,6 +641,84 @@ async function payloadCumplidoManifiesto(m: Row): Promise<string> {
622641 return xml ;
623642}
624643
644+ // ---------- Anulación payloads (RNDC procesos 9 / 28 / 29 / 32 / 54) ----------
645+ //
646+ // Pure builders (NIT + motivo + observaciones as params) so they can be
647+ // characterization-tested for exact XML without a DB. Motivo codes differ per
648+ // proceso: cumplidos (28/29/54) usan D/O; manifiesto (32) D/S/R; remesa (9) D/S.
649+ // OBSERVACIONES es opcional en el RNDC pero siempre se emite (aunque vacía) para
650+ // calzar con los XML de referencia del Ministerio.
651+
652+ /** Always-present <OBSERVACIONES> tag (matches the RNDC reference XMLs). */
653+ function tagObservaciones ( obs : string ) : string {
654+ return '<OBSERVACIONES>' + RndcClient . escaparXml ( obs ?? '' ) + '</OBSERVACIONES>' ;
655+ }
656+
657+ /** procesoid 29 — Anular Cumplido de Manifiesto. */
658+ export function payloadAnularCumplidoManifiesto ( nit : string , numManifiesto : string , motivo : string , obs = '' ) : string {
659+ const vars : RndcVars = {
660+ NUMNITEMPRESATRANSPORTE : nit ,
661+ NUMMANIFIESTOCARGA : numManifiesto ,
662+ CODMOTIVOANULACIONCUMPLIDO : motivo , // D / O
663+ } ;
664+ return RndcClient . renderVariables ( vars ) + tagObservaciones ( obs ) ;
665+ }
666+
667+ /** procesoid 28 — Anular Cumplido de Remesa. */
668+ export function payloadAnularCumplidoRemesa ( nit : string , numRemesa : unknown , motivo : string , obs = '' ) : string {
669+ const vars : RndcVars = {
670+ NUMNITEMPRESATRANSPORTE : nit ,
671+ CONSECUTIVOREMESA : consecutivoRemesaRndc ( numRemesa ) ,
672+ CODMOTIVOANULACIONCUMPLIDO : motivo , // D / O
673+ } ;
674+ return RndcClient . renderVariables ( vars ) + tagObservaciones ( obs ) ;
675+ }
676+
677+ /**
678+ * procesoid 54 — Anular Cumplido Inicial de Remesa. Se envía sólo de forma
679+ * reactiva: cuando el RNDC rechaza un anular_cumplido_remesa / anular_remesa
680+ * exigiendo que primero se anule el cumplido inicial (ver remediación, Fase 2).
681+ */
682+ export function payloadAnularCumplidoInicialRemesa (
683+ nit : string ,
684+ numRemesa : unknown ,
685+ numManifiesto : string ,
686+ motivo : string ,
687+ obs = '' ,
688+ ) : string {
689+ const vars : RndcVars = {
690+ NUMNITEMPRESATRANSPORTE : nit ,
691+ CONSECUTIVOREMESA : consecutivoRemesaRndc ( numRemesa ) ,
692+ NUMMANIFIESTOCARGA : numManifiesto ,
693+ CODMOTIVOANULACIONCUMPLIDO : motivo , // D / O
694+ } ;
695+ return RndcClient . renderVariables ( vars ) + tagObservaciones ( obs ) ;
696+ }
697+
698+ /** procesoid 32 — Anular Manifiesto de Carga. */
699+ export function payloadAnularManifiesto ( nit : string , numManifiesto : string , motivo : string , obs = '' ) : string {
700+ const vars : RndcVars = {
701+ NUMNITEMPRESATRANSPORTE : nit ,
702+ NUMMANIFIESTOCARGA : numManifiesto ,
703+ MOTIVOANULACIONMANIFIESTO : motivo , // D / S / R
704+ } ;
705+ return RndcClient . renderVariables ( vars ) + tagObservaciones ( obs ) ;
706+ }
707+
708+ /**
709+ * procesoid 9 — Anular Remesa Terrestre de Carga. MOTIVOREVERSAREMESA='A'
710+ * (anular; 'L' sería liberar para transbordo, que no usamos aquí).
711+ */
712+ export function payloadAnularRemesa ( nit : string , numRemesa : unknown , motivo : string , obs = '' ) : string {
713+ const vars : RndcVars = {
714+ NUMNITEMPRESATRANSPORTE : nit ,
715+ CONSECUTIVOREMESA : consecutivoRemesaRndc ( numRemesa ) ,
716+ MOTIVOREVERSAREMESA : 'A' ,
717+ MOTIVOANULACIONREMESA : motivo , // D / S
718+ } ;
719+ return RndcClient . renderVariables ( vars ) + tagObservaciones ( obs ) ;
720+ }
721+
625722// ---------- Reads for the monitor ----------
626723
627724const FILTROS : Record < string , string [ ] > = {
0 commit comments