#!/usr/bin/perl

use 5.040.4;

# esta versión ~ 24.04 LTS incluye utf8 y va bien
# use integer;              # esto puede dar muchos problemas si se 'toca' el número con algo de multiplicar o dividir, mmm... 
# estamos hablando de cajeros y no de cepillos

my $version = 0.053;   # ver un poco todo

# vie 12 jun 2026 00:47:33 CEST
# jue 11 jun 2026 22:47:41 GMT

# para la versión 2, p.ej. elegir x billetes de y € = tanto < $ARGV[0]
# todo lo demás que simplemente cuadre. (v2)

# PRIMERO LO MAYOR, SI ES 0, VALE

# que si hay varias opciones cuadre mejor con los contadores particulares (v3)

# my $DEBUG = 0;   # se quita para que no cause confusión, no se usa

# idea original, c/Palacio Valdés -> Manolo Menédez (2º curso; Caja de Ahorros () -> UNICAJA,
# pero hay cajas no pertenecientes a UNICAJA, supongo, al menos legalmente, aunque sean del
# grupo sin participación significativa      (la lista de ent. fin. era laaaaaaaaaarga)

# mar 19 may 2026 10:17:34 CEST

# en cuanto a que este alogritmo es para CADA "intento de operación", se puede encargar
# el OS o bien el propio `perl` en línea de comandos o fichero batch de lotes sobre shell

# man perlrun
# la opción -n para el shebang no procede, porque este algoritmo es para cada intento de intento
# luego es normal que además de «depurar» el código se haga un `time perl $0`; en un batch, para
# meter en un batch típico (y a su vez dentro de un crontab, (@reboot), etc)
# 
# Idea para el OUTPUT: El OUTPUT _debe_ ser fichero con timestamp (lo hace el propio fichero,
# por lo que sólo podría registrarle por línea CSV 
# $counter, hour:min, amount, .+ (campos donde van el número de billetes de cada tipo)
#
# Pero: La idea de el algoritmo es facilitar llaa entrega de billetes pequeños que antes eran 
# difíciles de combinar. Esto es importante. Y además ayuda a gente de bajo poder adquisitivo.
# y puede enlazarse vía 'red' con algun tipo de auth futura -> institución pública /* Ministerio
# que corresponda */ y -> a su vez enlazado con EUROPEAN (zona euro, o no) PID o lo que corresponda
#
# temita verano: tipo de billete: divisa $, libra, .+
#

#########################
# *** HARDCODED *** AUTOTELLER
my $cantidad_minima = 10;                 # ¿por qué no? hmmm... era 20 en centro de Gijón
my $minbillete = 5;
#########################
# *** SOFTCODED *** MONEY DEMAND
my $saque = $ARGV[0];                             # ya se sabe cuánto debe salir y es comprobación final
unless (defined $ARGV[0]){
    
usage:      
    say "";  
    say "Usage $0: $0 <tanto>";
    say "";
    say " tanto = cantidad entera y mútiplo de $minbillete";
    say ""; 
    say "(c) Jesús Lozano Mosterín, 2026";
    say "";
    say "En este cajero la cantidad mínima a";
    say "sacar es de   $cantidad_minima €";
    say "";
    say "Esto es una prueba de concepto (jue 11 jun 2026 23:57:15 CEST)";
    say "Aim: ampliar posibilidades según disponible, 'tipos de' \n";
    exit -1;
}

my $resto = $saque;
say "\nDinero $saque unidades monetarias a desglosar:\n";

reinit:                                     # INICIO O REINICIO

my @counter = 'a' .. 'z';
my $c = 0;

my %numero = (
    'cero' => 0,
    'zero' => 0,
    '0' => 0,
    'uno' => 1,
    'one' => 1,
    '1' => 1,    
    'dos' => 2,
    'two' => 2,
    'due' => 2,
    '2' => 2,
    'three' => 3,
    'tres' => 3,
    'ter' => 3,
    '3' => 3,    
    'cuatro' => 4,
    'four' => 4,
    '4' => 4,
    'cinco' => 5,
    'five' => 5,
    '5' => 5,
    'seis' => 6,
    'six' => 6,
    '6' => 6,
    'siete' => 7,
    'seven' => 7,
    '7' => 7,
    'ocho' => 8,
    'eigth' => 8,
    '8' => 8,
    'nueve' => 9,
    'nine' => 9,
    '9' => 9,
    'diez' => 10,
    'ten' => 10,
    '10' => 10,
);      # máximo diez billetes de cada tipo

# hash = reverse %hash esd posible, pero NO en listas porque las listas NO guardan los índices. AH, no lo sabíais?
# Por eso, una lista o array es más eficiente en todos los sentidos que un hash o array nominal. Menos espacio, más velocidad.
# A aprender a programar en C otra vez. Es broma.  (c) Jesús Lozano Mosterín, 2026

# my $i = 0;    # raro es el programa en que no se usa esta variable

# chequeo de imposibilidad???
die "ERROR TOTAL. EXIT.\n\n" unless ($saque =~ /\d*[5|0]$/);                                                          # SALIDA 1

die "ERROR TOTAL. $saque unidades monetarias MENOR QUE CANTIDAD MÍNIMA $cantidad_minima unidades monetarias\n\n"
  if ($cantidad_minima > $saque);                                                                                     # SALIDA 2

################################

my @billete;
@billete = (250,200,100,50,20,10,5);             # pensado para euros €, se menciona unidades monetarias
                                            # se trata de que cuando se elige primero tanto del tipo x, ese tipo se elimina de la lista,
                                           # y se presenta como ópción lo siguiente más factible. Es decir, se puede usar incluso opt
                                           # heurística si no tarda mucho. No creo que lo haga. Por ejemplo si se obliga a elegir primero
                                           # número del billeta más grande y no se deja libertad para otros. Puede ser 0 cantidad.

# Los 250 son billetes de dólares y con la cara de Trump y Melania. Dedicado a vosotros y a todos los religiosos.

my $sum = 0;                              # lo seleccionado
my $subtotal = 0;                         # el corriente en la elección, subtotal  

# my @letes = (50);                        # no se sigue a efectos prácticos ~ 2026 (o sí, ¿cuanto se
# tardaría en probarlo en 1 solo cajero de Gijón?). Yo creo que bastante, porque muchos OS de los 
# cajeros no son preciosamente "POSIX" que en su día ~ 'lo que está en común OS diferentes (lista grande)  
# (pedantic mode)

# como se opta por eliminar combinaciones por importe hay que hacer copia de la lista para
# cada opción de cantidad de dinero

say "MENSAJE DE MODO DE SELECCIÓN";
say "----------------------------\n";
say "La lista de billetes disponibles es:   @billete   unidades monetarias\n";

my @report;

ini:
my $flag = 0;   

input:                               # esto nunca me había pasado :-)

say "NÚMERO DE BILLETES DE $billete[0] unidades monetarias. SU ELECCIÓN: (PUEDE SER 0):";
say "cantidad (0 posible) + RETURN.     Tecle el número:";
print "$_ " for ( sort { $a cmp $b } keys %numero );
say "\nAl haber tecleado '0' s ele pide confirmación";
print "\n ===> "; 
chomp (my $num = <STDIN>);
$num = $numero{ $num };
if ( defined $num && $num =~ /\D+/ ){ goto input; }              # REBOTE ARRIBA
# posibilidad de doble comprobación de cero para saltar de tipo de billete         
                                                                                 
# 0j0: SI el $minbillete = 10 y hay uno de 25 ya NO SE CUMPLE!!! toda desmultiplicación o factorización
# pero re0j0 que lo único que importa es dar a la gente la cantidad de pide, y si te apañas, bien  
# (para eso estamos)

if ($num == 0){
    say "Cantidad   0   o no definida, NULA\n";
    if (++$flag == 1){
	goto input;
    }
    shift @billete;
    goto ini;
}

$subtotal = $num * $billete[0];
if ( $subtotal <= $resto ){                        # puede ser
    $resto -= $subtotal;
    push @report, "$counter[$c++]) --->   $num unidades   de   $billete[0] unidades monetarias    \=     $subtotal unidades monetarias.     RESTO = $resto\n\n";
    print $report[-1];
    $sum += $subtotal;
    shift @billete;                                # se anula la posibilidad de ese tipo de billetes en la lista mostrada
    
    if (scalar(@billete) == 1){
	# esto es operación riesgosa, SLU
	# se decide el último tipo de billete, sin preguntar o sugiriendo al usuario
	# Podría ser decir: O $num de billetes más pequeños disponibles o reiniciar
	$num = $resto / $billete[-1];
	push @report, "$counter[$c++]) --->   $num unidades   de   $billete[0] unidades monetarias    \=     $subtotal unidades monetarias.     RESTO = $resto\n\n";
	print $report[-1];
	if ($num == int($num)){
	    goto salida;
	}else{
	    # hay un resto, positivo o negativo y se continúa 
	}
    }
    
    if ($resto == 0){
	if ($saque == $sum){
	    say "Check... ok\n";
	    goto salida;
	}else{  die "Error: check not OK!\n\n";  }
    }
    
    if ($resto < $minbillete && $resto >= 0) {
	warn "Error: Hay un resto pequeño. Cantidad errónea";
	say "REINICILIZACIÓN\n";
	goto reinit;               # reinit
    }
}
goto ini;   # circumstances

salida:

say "\nSUMA TOTAL A RETIRAR \= $saque\n";                                            # SALIDA 3 BUENA

print @report;                # listadillo

say "\nFinished transaction by $0 v$version;      ", scalar(localtime()), "\n"; 
exit 2;

__END__

Me gusta mirar cajeros. Un día que pìerda el teléfono, espero comm
  por ellos. Son confiables. Y prácticamente exactos. 
La intención es buena. En Ibercaja saben de repartir billetes 
  pequeños para ayudar a jóvenes y mayores de baja renta.
  Pero no tienen algoritmo de ayuda. Vamos a ver.
Actualización: en Gijón hay unos 4 con funcionamiento raro. Dos
  no dan dinero ni ponen mensaje, uno no hace transferencias
  y otro no tiene sensibilidad suficiente para la transacción.
  Normalmente una dura menos de 2 minutos.
	  
