Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
1.34% covered (danger)
1.34%
22 / 1639
2.22% covered (danger)
2.22%
3 / 135
CRAP
n/a
0 / 0
ia_sqlErrors
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
ia_update
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
210
ia_dontQuote
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
110
ia_insert
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
132
valExisteFile
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
ia_fileName
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
ia_guid
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
ia_OptionsSetData
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
210
ia_Options_array
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
342
ia_Options_KeyDisplay
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
42
strit
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
strit_array
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
fieldit
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
strim
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
stritc
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
comillea
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
jsit
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
javait
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
param
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
param_get
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
param_post
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
_keyTrimmed
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
getParams
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
arrayDiff
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
650
array_is_list
n/a
0 / 0
n/a
0 / 0
2
array_list_diff
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
132
file_upload_error
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
90
uploadErrorStr
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
90
ip_extract
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
ip_server_set
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
ip_get
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
ip_getFull
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
redirect
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
42
siteRootUrl
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
ia_convertBytes
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
ia_errores_a_dime
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
ia_report_status_collapsable
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
file_size_formatted
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
file_is_image
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
file_icon
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
182
ia_image_tag_size
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
ia_img_format
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
ia_image_tagsize_fitmax
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
1122
removeAccents
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
filename_safe
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
filename_extension
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
error_last
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
gAppRelate_set
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
array_val
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
_numOr0
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
date_limit
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
702
to_plural
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
380
to_label
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
ia_mktime_day
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
array2Select
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 1
650
colores
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
die_Script
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
json_encode_ex
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
table_jqgrid_existencias
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
table_productos_remate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
keyRemap
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
getLabelBodega
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
240
getUrlBase
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
getUrlTo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
unsetItem
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
array_sort
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
90
getEnums
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
adjustBrightness
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
limpiaDatos
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
searchValueInArrayByColumn
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
get_url_origin
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
182
get_full_url
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
get_url_out_params
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
notasFaltantes
n/a
0 / 0
n/a
0 / 0
1
bodegasResumenNumeroNotas
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
90
miniLog
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
272
bcformat
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
numeric2string
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
bcabs
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
producto_color_reset
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
210
me_pregunto_notas_bodega
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
90
ia_htmlentities
50.00% covered (danger)
50.00%
4 / 8
0.00% covered (danger)
0.00%
0 / 1
8.12
mysqlDate2display
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
72
mysqlDateTime2display
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
mysqlTimestamp2display
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
color_id_nombre
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
color_id_activo
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
producto_general_id_nombre
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
producto_general_id_activo
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
checked
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
56
selected
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
56
statusBodegaSelect
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
jsonRegExp
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
jsonLike
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
strlike
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
fixJsonBuscaEnProperty
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
getFueErrorNo
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
fueErrorIguales
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
fueErrorConfirmaIguales
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
itemsIguales
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
campoAtipo
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
compareCaseInsensitive
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
90
diasEntreFechas
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
fechaDiff
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
156
keyOrder
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
keyOrderEnd
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
getCurrentValueJson
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
displayColumn
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 1
702
viewJsCode
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
is_container_number
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
formatMe
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
702
formatMeArray
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
formatMeId
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
110
the_error_handler
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
the_exception_handler
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
recomiendanOlla
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
240
recomiendanOllaTable
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
recomiendaSave
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 1
650
topVentasBodega
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
20
usuario_defaults_get
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
usuario_defaults_set
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
usedFilesTimes
0.00% covered (danger)
0.00%
0 / 112
0.00% covered (danger)
0.00%
0 / 1
1980
getMTime
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
getLastNModifiedFiles
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
file_debug_reporte
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
272
keys2Tiny
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
56
num2Key
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
getDeviceInfo
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
jqGridColsSorter_SelectLinkTo
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
56
jqGridColsSorter_GetDefs
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
units2grid
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
jsCodeColChooser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
bodega_x_dia_cache
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
bodega_x_dia_ExistenciaMaximaTable
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
bodega_x_dia_recalc
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/** @noinspection PhpRedundantOptionalArgumentInspection */
3/** @noinspection PhpMissingReturnTypeInspection */
4/** @noinspection PhpMissingParamTypeInspection */
5/** @noinspection RegExpRedundantEscape */
6
7/** 2021-12-21 Todo verde */
8
9/**
10 * ia_util.php
11 * Utilerias
12 * @version 2.1.2012.09.23
13 * @package utilerias
14 * pre-requisites: include config.php para array $gIAsql y $gIaTimeStart value,
15 *
16 */
17
18use JetBrains\PhpStorm\ExpectedValues;
19use JetBrains\PhpStorm\NoReturn;
20use JetBrains\PhpStorm\Pure;
21
22// ////////////////////// SQL  //////////////////////
23global $gIAsql;
24    if(!array_key_exists('set_autocommit',$gIAsql)) $gIAsql['set_autocommit']=null;
25    if(!array_key_exists('pconnect',$gIAsql)) $gIAsql['pconnect']=false;
26    if(!array_key_exists('trace',$gIAsql)) $gIAsql['trace']=false;
27    if(!array_key_exists('sql_trace',$gIAsql)) $gIAsql['sql_trace']=array();
28    if(!array_key_exists('link',$gIAsql)) $gIAsql['link']=null;
29    if(!array_key_exists('err',$gIAsql)) $gIAsql['err']='';
30    $gIAsql['affected_rows']=$gIAsql['begins']=$gIAsql['selected_rows']=0;
31
32$gIAsql['dont_quote']=array('NULL','CURDATE()','CURRENT_DATE()','CURRENT_DATE','CURRENT_DATETIME','CURRENT_TIME()','CURRENT_TIME','CURTIME()'
33    ,'CURRENT_TIMESTAMP()','CURRENT_TIMESTAMP','NOW()','LOCALTIMESTAMP','LOCALTIMESTAMP()','SYSDATE()','UNIX_TIMESTAMP()'
34    ,'UTC_DATE()','UTC_TIME()','UTC_TIMESTAMP()');
35
36include_once(__DIR__.'/sqlConverter.php');
37/**
38 * ia_sqlErrors()
39 *
40 * @return string html <ul> con los errores
41 */
42function ia_sqlErrors():string {
43global $gIAsql;
44    return $gIAsql['err'] === '' ? '' : "<ul>$gIAsql[err]</ul>";
45}
46
47/**
48 * ia_update()
49 * regresa un update $table set $key='$values[$key]',... WHERE  $pkey='$primaryKey[$pkey]' AND
50 * @param string $table nombre de la tabla
51 * @param array $values 'campo'=>value, rutina protege apostrofes
52 * @param string|array $where en array hace 'campo'=value  con AND, en string lo pone
53 * @param array $excludeQuote
54 * @param string $after_clause despues del where.
55 * @param string $before_clause entre update y table.
56 * @param boolean $inteligent_quotes en true no quotea lo que este en $gIAsql['dont_quote']
57 * @param boolean $dontQuote en true no pone quotes alrededor de los valores, default false no pone quotes
58 * @return string
59 */
60function ia_update($table, $values = [], $where = [],
61                   $excludeQuote=[], $after_clause='', $before_clause='',
62                   $inteligent_quotes = true, $dontQuote = false):string {
63global $gIAsql;
64    $upd='';
65    foreach($values as $fieldName=>$v) {
66        if( is_null($v) )
67            $upd.="$fieldName=NULL";
68        elseif( $dontQuote || ($inteligent_quotes && in_array(strtoupper(trim($v)),$gIAsql['dont_quote'])) || in_array($fieldName,$excludeQuote) || ia_dontQuote($v) )
69            $upd.="$fieldName=$v";
70        else
71            $upd.="$fieldName=".strit($v);
72    }
73    if(is_array($where)) {
74        $w='';
75        foreach($where as $fieldName=>$v)
76            if( is_null($v) )
77                $w.=" AND $fieldName IS NULL";
78            elseif( $dontQuote || ($inteligent_quotes && in_array(strtoupper(trim($v)),$gIAsql['dont_quote'])) )
79                $w.=" AND $fieldName = $v";
80            else
81                $w.=" AND $fieldName = ".strit($v);
82        $w=substr($w,5);
83    } else
84        $w=$where;
85
86    $comment = "/** ACTUALIZA $table **/";
87    return "UPDATE $comment $before_clause $table SET ".substr($upd,1)." WHERE $w ".$after_clause;
88}
89
90
91function ia_dontQuote( $v):bool {
92    $paren = strpos($v,'(');
93
94    if($paren === FALSE)
95        return false;
96    $close = strpos($v,')',$paren);
97
98    if($close === FALSE  || $close < $paren)
99        return false;
100    $blank = strpos($v,' ');
101    if($blank === FALSE || $paren < $blank && substr($v,-1==')') && substr_count ($v,'(') == substr_count ($v,')')  ) {
102        $functions = array('DATE_ADD(','DATE_SUB(');
103        foreach($functions as $f)
104            if(stripos($v,$f) === 0)
105                return true;
106    }
107    return false;
108}
109
110/**
111 * ia_insert()
112 * regresa un insert $before_clause into $table($keys) values($vals) $after_clause;
113 * @param string $table nombre de la tabla
114 * @param array $values 'campo'=>value
115 * @param boolean $autoOnUpdate en true hace ON DUPLICATE UPDATE de no venir en after_clause
116 * @param string $after_clause poner despues de values: on duplicate key...
117 * @param string $before_clause entre insert y (campos)
118 * @param boolean $inteligent_quotes en true no quotea lo que este en $gIAsql['dont_quote']
119 * @param boolean $dontQuote en true no pone quotes alrededor de los valores, default false no pone quotes
120 * @return string
121 * VCA 29-07-2021 hice una actualizacion a ON DUPLICATE KEY UPDATE
122 */
123function ia_insert($table, $values = [], $excludeQuote = [], $after_clause='',
124       $autoOnUpdate=false, $before_clause='', $inteligent_quotes=true, $dontQuote=false, $comment = ''): string
125{
126global $gIAsql;
127    $ins='';
128    $val='';
129    $upd='';
130
131    $TB = strtoupper($table);
132    if (empty($comment))
133        $comment = "/** INSERTA $TB **/";
134
135    $alias = $table."_alias";
136
137    foreach($values as $fieldName=>$v) {
138        $ins.=",$fieldName";
139        if( is_null($v) )
140            $val.=",NULL";
141        elseif(is_array($v)) {
142            $val.=','.strit( implode(',',$v));
143        } elseif( $dontQuote || ($inteligent_quotes && in_array(strtoupper(trim($v)),$gIAsql['dont_quote'])) || in_array($fieldName,$excludeQuote) || ia_dontQuote($v) )
144            $val.=",$v";
145        else
146            $val.=','.strit($v);
147        $upd.=",$fieldName=$alias.$fieldName";
148    }
149    if($autoOnUpdate)
150        $after_clause=" AS $alias ON DUPLICATE KEY UPDATE ".substr($upd,1);
151    return "INSERT $comment $before_clause INTO $table(".substr($ins,1).") VALUES(".substr($val,1)."$after_clause";
152
153}
154
155/**
156* ia_nameFile($Path, $elName)
157*
158* Path: para ubicaicón del archivo
159* elName: Nombre original del archivo
160*
161*/
162function valExisteFile($Path, $elName, $laextension, $cntInicial):string {
163    if(file_exists($Path."/".$elName.".".$laextension)){
164        if(!str_contains($elName, "--_")){
165            return valExisteFile($Path, $elName."--_".$cntInicial, $laextension, ++$cntInicial);
166        }
167        $nombreAnt = explode("--_",$elName);
168        return valExisteFile($Path, $nombreAnt[0]."--_".$cntInicial, $laextension, ++$cntInicial);
169    }
170    return $elName.".".$laextension;
171}
172
173function ia_fileName($Path, $elName, $gWebDir):string {
174    $elName   = sanitizeFileName($elName);
175    $fpp      = pathinfo($elName);
176    $elName   = array_key_exists('filename',$fpp) ? strtolower($fpp['filename']):'';
177    $elName   = substr($elName,0,50);
178    $laextension = array_key_exists('extension',$fpp) ? strtolower($fpp['extension']):'';
179
180    $newName   = valExisteFile($Path, $elName, $laextension, 1);
181    valida_carpetas($Path,$gWebDir);
182
183    return $newName;
184}
185
186/**
187 * ia_guid()
188* returns universal uuid de mysql: sin - en base 36 (29 caracteres maximo) reversed para menos carga a indices.
189
190* @param string $cmnt
191* @return string uuid
192 */
193function ia_guid($cmnt='guid'):string {
194    $a=explode('-',ia_singleread("SELECT SQL_NO_CACHE /* $cmnt */ UUID()",'',false));
195    return $a[4].$a[3].$a[2].$a[1].$a[0];
196    // aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
197}
198
199/**
200 * ia_SqlOptionsSetDataData()
201 *
202 * @param array $arr
203 * @param string|array $selected
204 * @param array $extra
205 * @param string $optionTag
206 * @return string
207 */
208function ia_OptionsSetData($arr, $selected='', $extra = [], $optionTag=''):string {
209
210    $ret='';
211    if($extra) foreach($extra as $k=>$v)
212        if(is_array($selected)) {
213            $ret.= PHP_EOL."<option value='$k".(array_key_exists($k,$selected) ? " SELECTED='selected' " : " ")." $optionTag >".ia_htmlentities($v)."</option>";
214        } else {
215            $ret.= PHP_EOL."<option value='$k".(strcmp($selected,$k) ? " " : " SELECTED='selected' ")." $optionTag >".ia_htmlentities($v)."</option>";
216        }
217    foreach($arr as $k=>$v) {
218        if(is_array($selected))
219            $ret.= PHP_EOL."<option value='$k'".(array_key_exists($k,$selected) ? " SELECTED='selected' " : "");
220        else
221            $ret.= PHP_EOL."<option data-data='$k' value='$k'".(strcmp($selected,$k) ? "" : " SELECTED='selected' ");
222        $i=0; $label='';
223        if(is_array($v))
224            foreach($v as $attr=>$val) {
225                if($i === 1)
226                    $label=$val;
227                if( $i > 1 )
228                    $ret.=" data-$attr='$val'";
229                $i++;
230            }
231        else
232            $label=$v;
233
234        $ret.=" $optionTag>".ia_htmlentities($label)."</option>";
235    }
236    return $ret;
237}
238
239///////////////////////
240/**
241 * ia_Options_array()
242 * pone options de un <select>
243 *
244 * @param array $array [value]=display o value=array(value,display,'html-tag'=>tag_value,,'html-tag'=>tag_value,...)
245 * @param string $selected seleccionado actualmente
246 * @param string $optionAttr opcional, atriubtos a agreagar a cada option
247 * @param string $setColor true agrega style="color:color" de existir color en el $array
248 * @param null $value
249 * @param null $label
250 * @return string
251 */
252function ia_Options_array($array, $selected = '', $optionAttr = '', $setColor = '', $value = null, $label = null):string {
253    $pon=array('class','style','title');
254    $ret='';
255    $colorStyle = '';
256    if( !empty( $array) ) foreach($array as $key =>$d) {
257        $opts='';
258        $id='';
259        $text='';
260
261        if(is_array($d) || is_object($d)) {
262            $i=0;
263            if($setColor !== '' )
264                $colorStyle = array_key_exists($setColor, $d) ?
265                    ' style="color:' .
266                    ia_htmlentities($d[$setColor] === '#FFFFFF' ? 'black' : $d[$setColor]) . '" ' :
267                    '';
268            foreach($d as $k => $v) {
269                // dd_('jaja', $d);
270                $i++;
271                if($i===1)
272                    $id=$v;
273                elseif($i===2)
274                    $text=$v;
275                elseif($i>2) {
276                    if(in_array($k,$pon))
277                        $opts.=" $k='".ia_htmlentities($v)."'";
278                    else
279                        $opts.=" data-$k='".ia_htmlentities($v)."'";
280                }
281
282                if (!empty($value)) {
283                    $id = $d[$value];
284                }
285                if (!empty($label)) {
286                    $text = $d[$label];
287                }
288            }
289        } else {
290            $text = $d;
291            $id = $key;
292        }
293
294        if(is_array($selected)) {
295            $ret.="\r\n<option $colorStyle value='$id".(in_array($id,$selected) ? " SELECTED='selected' " : "")." $optionAttr$opts>".ia_htmlentities($text)."</option>";
296        } else {
297            $ret.="\r\n<option $colorStyle value='$id".(strcmp($selected,$id) ? "" : " SELECTED='selected' ")." $optionAttr$opts>".ia_htmlentities($text)."</option>";
298        }
299    }
300    return $ret;
301}
302
303/**
304 * ia_Options_KeyDisplay()
305 * pone options de un <select>
306 *
307 * @param array $array            [value]=display
308 * @param array|string|null $selected         value seleccionado actualmente
309 * @param string $optionAttr        opcional, atriubtos a agreagar a cada option
310 * @return string
311 */
312function ia_Options_KeyDisplay($array, $selected='', $optionAttr=''):string {
313    $ret='';
314    if( !empty( $array) ) foreach($array as $k=>$d) {
315        if(is_array($selected)) {
316            $ret.="\r\n<option value='$k".(in_array($k,$selected) ? " SELECTED='selected' " : "")."$optionAttr>".ia_htmlentities($d)."</option>";
317        } else {
318            $ret.="\r\n<option value='$k".(strcmp($selected,$k) ? "" : " SELECTED='selected' ")."$optionAttr>".ia_htmlentities($d)."</option>";
319        }
320    }
321    return $ret;
322}
323
324/**
325* Portege un string o fecha de caracteres extra para el sql query
326* @param string|int|float|null $s el string a proteger
327* @return string el string $s protegido
328*/
329function strit($s):string  {
330    if($s === null)
331        $s = '';
332    if(is_array($s)) return "''"; return "'".str_replace ("\\", "\\\\",str_replace("'","''", $s))."'"; }
333
334/**
335 * Protege los valores del array con strit
336 *
337 * @param array $array
338 * @return array
339 */
340function strit_array($array):array {
341    foreach($array as &$d)
342        $d = strit($d);
343    return $array;
344}
345
346/**
347 * Protect with ` an sql name
348 * Quotes a: column/table/db name to `column name` respecting . table.column to `table`.`column`
349 *
350 * @param string $fieldName
351 * @return string
352 */
353function fieldit($fieldName):string {
354    if($fieldName === '' || $fieldName[0] === '(')
355        return $fieldName;
356
357    $protected = [];
358    $n = explode('.',$fieldName);
359    foreach($n as $field) {
360        $protected[]= '`'.str_replace(['`',"\r","\n","\t","\0"], '', strim($field) ).'`';
361    }
362    return implode('.', $protected);
363}
364
365    function strim($str):string|array {
366        if($str === null)
367            return '';
368        if(is_array($str)) {
369            foreach($str as &$d)
370                $d = strim($d);
371            return $str;
372        }
373        $s1 = preg_replace('/[\pZ\pC]/muS',' ',$str);
374        if(preg_last_error()) {
375            $s1 = preg_replace('/[\pZ\pC]/muS',' ',  iconv("UTF-8","UTF-8//IGNORE",$str));
376            if(preg_last_error())
377                return trim(preg_replace('/ {2,}/mS',' ',$str));
378        }
379        return trim(preg_replace('/ {2,}/muS',' ',$s1));
380    }
381
382
383/**
384* Portege un string o fecha de caracteres extra para el sql query
385* @param string|int|float|null $s el string a proteger
386* @return string el string $s protegido terminado con una coma
387*/
388function stritc($s):string {
389    if($s === null)
390        $s = '';
391    return "'".str_replace ("\\", "\\\\",str_replace("'","''", $s))."',"; }
392
393/**
394 * comillea()
395 *
396 * @param string|int|float|null $s
397 * @return string
398 */
399function comillea($s):string  { if($s===null) return '"null"';  return '"'.str_replace('"',"'", $s).'"'; }
400/**
401 * jsit()
402 *
403 * @param string|int|float|null $s
404 * @return string
405 */
406function jsit($s):string {
407    if($s === null)
408        return 'null';
409    return '"'.str_replace( array("\\","\"","\'"),array("\\\\","\\\"","\\\'"),$s).'"';
410}
411
412/**
413* Portege un string de javascript entre $entre apostrofes ' o comillas
414* @param string|int|float|null $s el string a proteger
415* @param string $entre ' o " El string sera puesto entre ' o "
416* @return string el string $s protegido
417*/
418function javait($s, $entre="'"):string  {
419    if($s === null)
420        return "null";
421    return $entre.str_replace ($entre, "\\'",str_replace("\\", "\\\\", $s)).$entre;
422}
423
424/////////////////////////////////// params ////////////////////////////////////////////////////
425
426/**
427* regresa el parametro llamado trimmed $name de $_REQUEST, si no existe busca en get, de no estar regresa $dflt
428* @param string $name nombre del parametro a regresar
429* @param mixed $dflt='' el valor a regresar de no existir el parametro. default ''
430* @return mixed el parametro llamado $name en post o get array si es array, de no estar regresa $dflt
431*/
432function param($name, $dflt='', $trim=true):mixed {
433    if(str_contains($name, "remark")) {
434        return $_REQUEST[$name] ?? $dflt;
435    }
436    if(str_contains($name, "coment")) {
437        return $_REQUEST[$name] ?? $dflt;
438    }
439    return $trim ? strim($_REQUEST[$name] ?? $dflt) :  $_REQUEST[$name] ?? $dflt;
440}
441
442
443/**
444* regresa el parametro llamado $name en get en string o array, de no estar regresa $dflt
445* @param string $name nombre del parametro a regresar
446* @param mixed $dflt='' el valor a regresar de no existir el parametro. default ''
447* @return mixed el parametro llamado $name en get, de no estar regresa $dflt
448*/
449#[Pure] function param_get($name, $dflt=''):mixed {
450    return _keyTrimmed($_GET, $name, $dflt);
451}
452
453/**
454* regresa el parametro trimmed llamado $name en post en string o array, de no estar regresa $dflt
455 *
456* @param string $name nombre del parametro a regresar
457* @param mixed $dflt='' el valor a regresar de no existir el parametro. default ''
458* @return mixed el parametro llamado $name en post, de no estar regresa $dflt
459*/
460#[Pure] function param_post($name, $dflt=''):mixed {
461   return _keyTrimmed($_POST, $name, $dflt);
462}
463
464/**
465 * regresa el parametro trimmed llamado $name del array $from en string o array, de no estar regresa $dflt.
466 *
467 * @param array $from
468 * @param string $name nombre del parametro a regresar
469 * @param mixed $dflt='' el valor a regresar de no existir el parametro. default ''
470 * @return mixed el parametro llamado $name en post, de no estar regresa $dflt
471 */
472 function _keyTrimmed($from,string $name, $dflt):mixed {
473    if( !array_key_exists($name,$from))
474        return $dflt;
475    if(is_array($from[$name])) {
476        $arr = [];
477        foreach($from[$name] as $k => $s) {
478            $arr[$k] = is_array($s) ? $s : strim($s);
479        }
480        return $arr;
481    }
482    return strim($from[$name]);
483}
484
485/**
486 * @param string|null $type null, post or get
487 * @return array null: $_REQUEST, post:$_POST, get:$_GET
488 */
489function getParams($type = null):array
490{
491    if($type === null)
492        return $_REQUEST ?? [];
493    return $type === 'post' ? $_POST ?? [] : $_GET ?? [];
494}
495
496/**
497 * Obtiene la diferencia entre 2 arrays. Ejemplo tests/ejemplo_arrayDiff.php
498 *
499 * @param array $a
500 * @param array $b
501 * @param float $delta para floats la diferencia debe ser mayor que
502 * @return array [$keyConValorDiferente_1=>[$a[$k1], $b[$k1]], ...]
503 *
504 * @example tests/ejemplos/arrayDiff.php
505 */
506function arrayDiff(array $a, array $b, float $delta = 1E-2):array {
507    $diff = [];
508    if(array_is_list($a) && array_is_list($b)) {
509        return array_list_diff($a, $b);
510    }
511    foreach($a as $k => $d) {
512        if(!array_key_exists($k, $b)) {
513            $diff[$k] = [$d, null];
514            continue;
515        }
516        $v = $b[$k];
517        if($d === $v)
518            continue;
519        if(is_array($d)) {
520            if(is_array($v)) {
521                if($d == $v)
522                    continue;
523                $subArrayDiff = arrayDiff($d, $v, $delta);
524                if(!empty($subArrayDiff))
525                    $diff[$k] = $subArrayDiff;
526                continue;
527            }
528            $diff[$k] = [$d, $v];
529            continue;
530        }
531        if(is_array($v) || is_null($d) || is_null($v)) {
532            $diff[$k] = [$d, $v];
533            continue;
534        }
535        if(is_float($d) && is_float($v) ||
536            is_numeric($d) && is_numeric($v)
537        ) {
538            // dd_($d,$v, abs($d), abs($v), bccomp($d, $v, 2));
539            // if(abs($d - $v) > $delta )
540//            if (bccomp($d, $v, 2)!=0)
541            if(abs($d - $v) > $delta )
542                $diff[$k] = [$d, $v];
543            continue;
544        }
545        if((is_string($d) || $d instanceof Stringable) &&
546            (is_string($v) || $v instanceof Stringable)
547        ) {
548            if(strcasecmp($d, $v))
549                $diff[$k] = [$d, $v];
550            continue;
551        }
552        if($d != $v)
553            $diff[$k] = [$d, $v];
554    }
555    foreach( array_diff_key($b, array_intersect_key($b, $a)) as $k => $soloEnB)
556        $diff[$k] = [null, $soloEnB];
557    return $diff;
558}
559
560if (!function_exists('array_is_list')) {
561    function array_is_list(array $a) {
562        return $a === [] || (array_keys($a) === range(0, count($a) - 1));
563    }
564}
565
566function array_list_diff(array $a, array $b, float $delta = 1E-2):array {
567    if(!array_is_list($a) || !array_is_list($b))
568        return arrayDiff($a, $b, $delta);
569    $diff = [];
570    $bLen = count($b);
571    foreach($a as $k => $aValue) {
572        if($k > $bLen - 1) {
573            $diff[$k] = [$aValue, null];
574            continue;
575        }
576        $bValue = $b[$k];
577        if($aValue === $bValue)
578            continue;
579        if(is_array($aValue)) {
580            if(is_array($bValue)) {
581                $d = array_list_diff($aValue, $bValue, $delta);
582                if(!empty($d))
583                    $diff[$k] = $d;
584                continue;
585            }
586            $diff[$k] = [$aValue, $bValue];
587            continue;
588        }
589        if(is_array($bValue)) {
590            $diff[$k] = [$aValue, $bValue];
591            continue;
592        }
593        if(in_array($aValue, $b)) {
594           continue;
595        }
596        $diff[$k] = [$aValue, $bValue];
597    }
598    return $diff;
599}
600
601function file_upload_error($err):string {
602    if( $err==0)
603       return '';
604   elseif( $err==1)
605          return 'El archivo es demasiado grande';
606   elseif( $err==2)
607          return 'El archivo es demasiado grande';
608   elseif($err==3)
609          return 'Solo subio parte del archivo';
610   elseif( $err==4)
611          return 'No subio el archivo';
612   elseif( $err==6)
613          return 'No subio el archivo a tmp';
614   elseif( $err==7)
615          return 'Error al escribir el archivo';
616   elseif( $err==8)
617          return 'Tipo de archivo invalido';
618   else
619    return 'Error al subir el archivo';
620}
621
622/**
623 * uploadErrorStr()
624 *
625 * @param string|int $indx
626 * @return string
627 */
628function uploadErrorStr($indx):string {
629    if( $_FILES[$indx]['error']==0)
630       return '';
631   elseif( $_FILES[$indx]['error']==1)
632          return 'El archivo es demasiado grande';
633   elseif( $_FILES[$indx]['error']==2)
634          return 'El archivo es demasiado grande';
635   elseif( $_FILES[$indx]['error']==3)
636          return 'Solo subio parte del archivo';
637   elseif( $_FILES[$indx]['error']==4)
638          return 'No subio el archivo';
639   elseif( $_FILES[$indx]['error']==6)
640          return 'No subio el archivo a tmp';
641   elseif( $_FILES[$indx]['error']==7)
642          return 'Error al escribir el archivo';
643   elseif( $_FILES[$indx]['error']==8)
644          return 'Tipo de archivo invalido';
645   else
646    return 'Error al subir el archivo';
647}
648
649/////////////////////////////////////// ip //////////////////////////////////////////
650/**
651 * ip_extract()
652 *
653 * @param string $ip
654 * @return array
655 */
656function ip_extract($ip): array
657{
658    if (filter_var($ip, FILTER_VALIDATE_IP))
659        return [$ip];
660    $array = [];
661    if (@preg_match("/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/", $ip, $array))
662        return $array;
663    $regexIPV6 = '/((([0-9a-fA-F]){1,4})\\:){7}([0-9a-fA-F]){1,4}/i';
664    $array = [];
665    if (@preg_match($regexIPV6, $ip, $array))
666        return $array;
667    if(str_contains(strtolower($ip), 'localhost'))
668        return ['localhost'];
669    return [];
670}
671/**
672 * ip_server_set()
673 *
674 * @param string $cual
675 * @return bool
676 */
677function ip_server_set($cual):bool {
678  if(  !array_key_exists($cual,$_SERVER) )
679     return false;
680  return   strcasecmp($_SERVER[$cual],'unknown') !== 0 && $_SERVER[$cual] !== '';
681}
682/**
683 * ip_get()
684 *
685 * @return string
686 */
687function ip_get():string {
688    $keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED','HTTP_FORWARDED_FOR','HTTP_FORWARDED',
689        'HTTP_CLIENT_IP','HTTP_VIA','HTTP_COMING_FROM','HTTP_X_COMING_FROM',
690        'REMOTE_HOST','REMOTE_ADDR'
691    ];
692    foreach($keys as $key)
693        if(array_key_exists($key, $_SERVER)) {
694            $ip = ip_extract($_SERVER[$key]);
695            if(count($ip) >= 1)
696                return $ip[0];
697        }
698    return '';
699}
700/**
701 * ip_getFull()
702 *
703 * @return string
704 */
705#[Pure] function ip_getFull():string {
706    $keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED','HTTP_FORWARDED_FOR','HTTP_FORWARDED',
707        'HTTP_CLIENT_IP','HTTP_VIA','HTTP_COMING_FROM','HTTP_X_COMING_FROM',
708        'REMOTE_HOST','REMOTE_ADDR'
709    ];
710    $fullIp = [];
711    foreach($keys as $key)
712        if(array_key_exists($key, $_SERVER))
713            $fullIp[] = "$key" . $_SERVER[$key];
714    return implode(', ', $fullIp);
715}
716
717////////////////////// HTML //////////////////////////////////////////////////////////////////////////
718
719  /**
720   * redirect()
721   *
722   * @param string $url
723   * @return void
724   */
725  function redirect($url) {
726   $requestProtocol = array_key_exists('SERVER_PROTOCOL',$_SERVER)  ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
727   $protocolArr = explode("/",$requestProtocol);
728   $protocolName = isset($protocolArr[1]) ? trim($protocolArr[0]) : 'HTTP/1.0';
729   $protocolVersion = isset($protocolArr[1]) ? trim($protocolArr[1]): '';
730   if (stristr($protocolName,"HTTP") && strtolower($protocolVersion) > "1.0" ) {
731     $httpStatusCode = 307;
732   } else {
733      $httpStatusCode = 302;
734   }
735   $httpStatusLine = "$requestProtocol $httpStatusCode Temporary Redirect";
736   @header($httpStatusLine, TRUE, $httpStatusCode);
737   @header("Location: $url");
738  }
739
740/**
741 * @return string root del site https://dominio/vitex  sin la diagonal final
742 */
743  function siteRootUrl():string {
744      global $gWebDir;
745      return
746        (empty($_SERVER['HTTPS']) ? "http://$_SERVER[SERVER_NAME]" : "https://$_SERVER[SERVER_NAME]") .
747          ":$_SERVER[SERVER_PORT]/$gWebDir";
748  }
749
750/**
751 * ia_convertBytes()
752 *
753 * @param string|int|float $bytes
754 * @return string
755 */
756function ia_convertBytes($bytes):string {
757    if(empty($bytes) || !is_numeric($bytes))
758        return $bytes;
759    if($bytes<0) {
760        $signo = '-';
761        $bytes *= -1;
762    } else
763        $signo='';
764    if($bytes<=1024) {
765        $decs=0;
766        $punto='';
767    } else {
768        $decs=2;
769        $punto='.';
770    }
771    $unit=array('b','Kb','Mb','Gb','Tb','Pb');
772
773    /** @noinspection PhpRedundantOptionalArgumentInspection */
774    return $signo.@number_format($bytes/@pow(1024,
775                ($i=floor(log($bytes,1024)))),
776            $decs,$punto,',').' '.$unit[$i];
777}
778
779
780/**
781 * ia_errores_a_dime()
782 *
783 * @param string $message
784 * @param string $file
785 * @param string $error_type
786 * @param int|string $line
787 * @param string $md5
788 * @param bool $showSqlTrace
789 * @return void
790 * @noinspection PhpUnusedParameterInspection
791 */
792function ia_errores_a_dime($message = '', $file = '', $error_type = 'Manual', $line = 0,
793                          $md5 = '', $showSqlTrace = false):void {
794global $gSqlClass, $gNewDime;
795    require_once(__DIR__ . '/ErrorReporter/iaErrorReporter.php');
796    $gNewDime = new iaErrorReporter($gSqlClass); // ia\Log\
797    if(!empty($message)) {
798        $gNewDime->errorManual($message, $file, $error_type, $line, $md5);
799    }
800    $gNewDime->process();
801}
802
803
804
805/**
806 *
807 *
808 * @param bool $scriptTime
809 * @param bool $rusage
810 * @param bool $ram
811 * @param bool $sqlErrors
812 * @param bool $sqlTrace
813 * @param bool $phpErr
814 * @return string
815 * @noinspection PhpUnusedParameterInspection
816 */
817function ia_report_status_collapsable(
818    $scriptTime=true, $rusage=true, $ram=true, $sqlErrors=true, $sqlTrace=true, $phpErr=true):string {
819    global $gNewDime, $gSqlClass, $gDebugging;
820    if(empty($gNewDime))
821        $gNewDime = new iaErrorReporter($gSqlClass);
822    if($gNewDime->displayErrors())
823        echo "<script>gDisplayErrorInPage = true; jQuery(function(){  _displayErrorInPage(); }); </script>";
824    else
825        echo "<script>gDisplayErrorInPage = false;</script>";
826    if($gDebugging)
827        file_debug_reporte();
828    return '';
829}
830
831
832// files
833/**
834 * file_size_formatted()
835 *
836 * @param string|int|float $size
837 * @return string
838 */
839function file_size_formatted($size):string {
840    $size = (float)$size;
841    if($size<1024)
842        return $size.'b';
843    if($size<1024*1024)
844        return round($size/1024.00).'Kb';
845    if($size<1024*1024*1024)
846        return round($size/(1024.00*1024.00),1).'Mb';
847    return round($size/(1024.00*1024.00*1024.00),1).'Gb';
848}
849
850/**
851 * file_is_image()
852 *
853 * @param string $fileName
854 * @return bool
855 */
856function file_is_image($fileName):bool {
857    $dot=strrpos($fileName,'.');
858    if($dot===FALSE)
859        return false;
860    $ext=substr($fileName,$dot+1);
861    return (strcasecmp($ext,'jpg') === 0 || strcasecmp($ext,'jpeg') === 0 ||
862        strcasecmp($ext,'gif') === 0 || strcasecmp($ext,'png') === 0 );
863}
864
865/**
866 * file_icon()
867 *
868 * @param string $fileName
869 * @return string
870 */
871function file_icon($fileName):string {
872global $gIApath;
873
874    $dot=strrpos($fileName,'.');
875    if($dot === FALSE)
876        return '';
877    $ext=substr($fileName,$dot+1);
878    if(strcasecmp($ext,'jpg')===0 || strcasecmp($ext,'jpeg')===0 ||
879        strcasecmp($ext,'gif')===0 || strcasecmp($ext,'png')===0 ||
880        strcasecmp($ext,'bmp')===0 || strcasecmp($ext,'ico')===0 )
881        return "$gIApath[WebPath]img/ext/jpg.gif";
882    if(strcasecmp($ext,'avi')===0 || strcasecmp($ext,'mpeg')===0 ||
883        strcasecmp($ext,'rm')===0 || strcasecmp($ext,'wmp')===0 )
884        return "$gIApath[WebPath]img/ext/video.gif";
885    $gifs=array('csv','doc','docx','gif','htm','html','jpeg','jpg','pdf','png','pps','ppsx','ppt','pptx','video','wav','xls','xlsx');
886    if(in_array($ext,$gifs))
887        return "$gIApath[WebPath]img/ext/$ext.gif";
888    return "$gIApath[WebPath]img/ext/clip.gif";
889}
890
891// IMAGES
892/**
893 * ia_image_tag_size()
894 *
895 * @param mixed $imageFile
896 * @return string
897 */
898function ia_image_tag_size(mixed $imageFile):string {
899try {
900    if(!file_exists($imageFile) )
901        return '';
902    $size = filesize($imageFile);
903    if($size === FALSE)
904        $size = '';
905    else
906        $size = file_size_formatted($size);
907    if(file_is_image($imageFile)) {
908        $arr = @getimagesize($imageFile); // 0=width, 1=height, 3=height, width tag
909        if($arr === FALSE || !is_array($arr) || sizeof($arr) < 4)
910            return $size;
911        return "w: $arr[0]px h:$arr[1]px $size";
912    }
913    return $size;
914} catch(Exception) { return ''; }
915}
916
917/**
918 * @param int|float|string $w
919 * @param int|float|string $h
920 * @param bool $cssFormat
921 * @return string
922 */
923function ia_img_format($w, $h, $cssFormat):string { if($cssFormat) return "width: $w"."px; height: $h".'px'; return "width='$w' height='$h'"; }
924
925/**
926 * ia_image_tagsize_fitmax()
927 * ajusta imagen al tamaño
928 * @param mixed $imageFile path fisico a imagen o array(0=>w,1=>h)
929 * @param string|int $max_width maximo width deseado, 0 cualsea
930 * @param string|int $max_height maximo height deseado, 0 cualsea
931 * @param bool $cssFormat true regresa css flase
932 * @param bool $enLarge
933 * @return string en $cssFormat true width:npx; height:xpx en false width='xpx' height='ypx'
934 */
935function ia_image_tagsize_fitmax( $imageFile, $max_width, $max_height,
936                                 $cssFormat=true, $enLarge=false):string {
937try {
938    if(is_array($imageFile))
939        $arr=$imageFile;
940    else {
941        if (!file_exists($imageFile))
942            return '';
943        $arr=@getimagesize($imageFile); // 0=width, 1=height, 3=height, width tag
944        if($arr===FALSE || !is_array($arr) || sizeof($arr)<4) return '';
945    }
946    $w=$arr[0];
947    $h=$arr[1];
948
949    if(!empty($max_width) && !empty($max_height) ) {
950
951        if($w==$max_width && $h==$max_height)
952            return ia_img_format($w,$h,$cssFormat);
953        if(!$enLarge && $w<=$max_width && $h<=$max_height)
954            return ia_img_format($w,$h,$cssFormat);
955        if($w==0 || $h==0)
956           return ia_img_format($max_width,$max_height,$cssFormat);
957        if($enLarge && $w <= $max_width && $h <= $max_height) {
958            if($w>$h)
959                return ia_img_format($max_width,round($max_width*$h/$w),$cssFormat);
960            else
961                return ia_img_format(round($max_height*$w/$h),$max_height,$cssFormat);
962        }
963        if( $w>$max_width && $h<=$max_height)
964            return ia_img_format($max_width,round($max_width*$h/$w),$cssFormat);
965        if( $w<=$max_width && $h>$max_height)
966            return ia_img_format(round($max_height*$w/$h),$max_height,$cssFormat);
967        // ambas mayores
968        if($w>=$h)
969            return ia_img_format($max_width,round($max_width*$h/$w),$cssFormat);
970        else
971            return ia_img_format(round($max_height*$w/$h),$max_height,$cssFormat);
972    }
973    if(empty($max_height)) {
974
975        if($w==$max_width)
976            return ia_img_format($w,$h,$cssFormat);
977        if( $w <= $max_width) {
978            if( $enLarge )
979                return ia_img_format($max_width,round($max_width*$h/$w),$cssFormat);
980            else
981                return ia_img_format($w,$h,$cssFormat);
982        }
983        return ia_img_format($max_width,round($max_width*$h/$w),$cssFormat);
984    }
985    if(empty($max_width)) {
986
987        if($h == $max_height)
988            return ia_img_format($w,$h,$cssFormat);
989        if($h <= $max_height) {
990            if( $enLarge ) {
991                return ia_img_format(round($max_height*$w/$h),$max_height, $cssFormat);
992            } else {
993                return ia_img_format($w,$h,$cssFormat);
994            }
995        }
996        return ia_img_format(round($max_height*$w/$h),$max_height,$cssFormat);
997    }
998    return ia_img_format($w,$h,$cssFormat );
999} catch(Exception) { return ''; }
1000}
1001
1002/**
1003 * removeAccents()
1004 *
1005 * @param string $str
1006 * @return string
1007 */
1008function removeAccents($str):string {
1009    //return URLify::filter($str); Ademas quita los caracteres raros https://github.com/jbroadway/urlify
1010    return URLify::downcode($str);
1011    //return strtr(utf8_decode($str), utf8_decode('àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ'), 'aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY');
1012}
1013
1014/**
1015 * filename_safe()
1016 *
1017 * @param string $fileName
1018 * @return string
1019 */
1020function filename_safe($fileName):string {
1021    if(str_starts_with($fileName, '.'))
1022        $fileName='_'.substr($fileName,1);
1023    return str_replace(array('|','>', '<', '&', ' ','(',')','-','*','?','!','|','`'.'´','"',"'".'..'.DIRECTORY_SEPARATOR,DIRECTORY_SEPARATOR,"\\","/",'[',']','{','}')
1024        ,'_',removeAccents($fileName));
1025}
1026
1027/**
1028 * filename_extension()
1029 *
1030 * @param mixed $fileName
1031 * @return string
1032 */
1033function filename_extension($fileName):string  {
1034    $pos=strrchr($fileName,'.');
1035    if($pos===FALSE || $pos==$fileName)
1036        return '';
1037    return substr($pos,1);
1038}
1039
1040/**
1041 * error_last()
1042 *
1043 * @return string
1044 */
1045function error_last():string  {
1046    if ( !function_exists('error_get_last'))
1047        return '';
1048    $tmp=error_get_last();
1049    if($tmp)
1050        return "Error($tmp[type]): $tmp[message] at $tmp[file] line $tmp[line]";
1051    else
1052        return '';
1053}
1054
1055
1056/// ia_case specific
1057    global $gAppRelate;
1058    /**
1059     * gAppRelate_set()
1060     *
1061     * @return void
1062     */
1063    function gAppRelate_set() {
1064    global $gAppRelate;
1065        if(  empty($gAppRelate) )
1066            $gAppRelate=new appRelate();
1067    }
1068
1069     /* @noinspection PhpUnused */
1070   // function vale_where() {}
1071
1072    /**
1073     * array_val()
1074     * Regresa el valor de key en el array o default de no existir
1075     * @param string $key
1076     * @param array $array
1077     * @param mixed $default
1078     * @return mixed valor de $key en el array o $default de no existir
1079     */
1080    function array_val($key, $array, $default = false): mixed
1081    {
1082        if(array_key_exists($key,$array))
1083            return $array[$key];
1084        return $default;
1085    }
1086
1087    function _numOr0($n):string|int|float {
1088        return is_numeric($n) ? $n : 0;
1089    }
1090
1091    /**
1092     * date_limit()
1093     *
1094     * @param int|float|string|null $lim
1095     * @param string|null $base_date
1096     * @param bool $incluye_dom
1097     * @param bool $incluye_sab
1098     * @param bool $forzaInicioAno
1099     * @return string
1100     */
1101    function date_limit($lim, $base_date = null,
1102                        $incluye_dom = true, $incluye_sab = true, $forzaInicioAno = false):string {
1103        if(empty($lim)) {
1104            return '';
1105        }
1106        if( preg_match('/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2]\d|3[0-1])$/', $lim)  ) {
1107            return $lim;
1108        }
1109        if( is_numeric($lim)) {
1110            if(strlen($lim ) === 4) {
1111                return "$lim-01-01";
1112            }
1113            if(strlen($lim ) > 6) {
1114                return Date('Y-m-d', $lim );
1115            }
1116        }
1117
1118        if(empty($base_date)) {
1119            $base_date = Date('Y-m-d');
1120        }
1121        $baseTimeStamp = strtotime($base_date);
1122        $base_year = _numOr0(substr($base_date,0,4) );
1123
1124
1125        $anos = $meses = $semanas = $dias = 0;
1126        $tmp=explode(' ',$lim);
1127        if($tmp) {
1128            foreach ($tmp as $d) {
1129                $d = trim($d);
1130                if (stripos($d, 'y') !== false) {
1131                    $anos += _numOr0(str_ireplace('y', '', $d));
1132                } elseif (stripos($d, 'm') !== false)
1133                    $meses += _numOr0(str_ireplace('m', '', $d));
1134                elseif (stripos($d, 'w') !== false) {
1135                    $semanas += _numOr0(str_ireplace('w', '', $d));
1136                }
1137                elseif (stripos($d, 'd') !== false) {
1138                    $dias += _numOr0(str_ireplace('d', '', $d));
1139                }
1140                else {
1141                    $dias += _numOr0($d);
1142                }
1143            }
1144        }
1145
1146        $fecha = mktime(0,0,0,
1147            (int)Date('n',$baseTimeStamp) + $meses,
1148            (int)Date('j',$baseTimeStamp) + $dias + ($semanas * 7),
1149            $base_year + $anos);
1150
1151        $notSab = !$incluye_sab;
1152        $notDom = !$incluye_dom;
1153        if($notDom || $notSab) {
1154            $diffDias = abs(round(($fecha - $baseTimeStamp) / 86400) + 1);
1155            $masDias = floor($diffDias / 7) * ($notDom && $notSab ? 2 : 1);
1156            $diasem = (int)date("w", $fecha);
1157            if($diasem === 6 && $notSab) {
1158                $masDias++;
1159            }
1160            if($diasem === 0 && $notDom) {
1161                $masDias++;
1162            }
1163            $diasem = (int)date("w", $baseTimeStamp);
1164            if($diasem === 6 && $notSab) {
1165                $masDias++;
1166            }
1167            if($diasem === 0 && $notDom) {
1168                $masDias++;
1169            }
1170            $fecha=mktime(0,0,0,
1171                (int)Date('n', $fecha),
1172                (int)Date('j', $fecha) + $masDias,
1173                (int)Date('Y', $fecha)
1174            );
1175        }
1176
1177        return $forzaInicioAno ? Date('Y-01-01', $fecha) : Date('Y-m-d',$fecha);
1178    }
1179
1180    /**
1181     * to_plural()
1182     *
1183     * @param string $word
1184     * @param bool $esMasculino
1185     * @return string
1186     */
1187    function to_plural($word, $esMasculino = true):string {
1188        $w=substr($word,-1);
1189
1190        if(strcasecmp('.',$w)==0) //VCA
1191            return $word;
1192
1193        if(    strcasecmp('a',$w)==0 || strcasecmp('e',$w)==0  || strcasecmp('i',$w)==0  || strcasecmp('o',$w)==0  || strcasecmp('u',$w)==0
1194            || strcasecmp('á',$w)==0 || strcasecmp('é',$w)==0  || strcasecmp('í',$w)==0  || strcasecmp('ó',$w)==0  || strcasecmp('Ú',$w)==0
1195            || strcasecmp('y',$w)==0 || strcasecmp('w',$w)==0 )
1196            return $word.'s';
1197        if(strcasecmp('z',$w)==0)
1198            return substr($word,0,-1).'ces';
1199        $w2=substr($word,-2,1);
1200        if(strcasecmp('s',$w)==0 && ( strcasecmp('e',$w2)==0 || strcasecmp('u',$w2) ))
1201            return $word;
1202        if($esMasculino)
1203            return $word.'es';
1204        else
1205            return $word.'as';
1206    }
1207
1208    /**
1209     * to_label()
1210     *
1211     * @param string $fieldName
1212     * @param bool $capWords
1213     * @param bool $htmlentities
1214     * @return string
1215     */
1216    function to_label($fieldName, $capWords = true, $htmlentities = false ):string {
1217        $ret=preg_replace(
1218            array('/\baccion\b/i','/\baleman\b/i','/\balmacen\b/i','/\bano\b/i','/\banos\b/i','/\barea\b/i','/\bareas\b/i','/\barticulo\b/i','/\barticulos\b/i'
1219                    ,'/\bbitacora\b/i','/\bbitacoras\b/i'
1220                    ,'/\bcalculo\b/i','/\bcaracter\b/i','/\bcatalogo\b/i','/\bcatalogos\b/i','/\bcodigo\b/i','/\bcomentario\b/i','/\bcompania\b/i','/\bcompanias\b/i','/\bcp\b/i'
1221                    ,'/\bdepostio\b/i','/\bdepostios\b/i','/\bdia\b/i','/\bdias\b/i','/\bdigito\b/i','/\bdigitos\b/i','/\bdolar\b/i','/\bdolares\b/i'
1222                    ,'/\beconomia\b/i','/\belectronica\b/i','/\belectronico\b/i','/\belectronicos\b/i','/\bespanol\b/i'
1223                    ,'/\binteres\b/i','/\bimagenes\b/i','/\bindice\b/i','/\bindices\b/i','/\bingles\b/i'
1224                    ,'/\bfabrica\b/i','/\bfrances\b/i','/\bfotografica\b/i'
1225                    ,'/\bgrafica\b/i'
1226                    ,'/\bhuesped\b/i'
1227                    ,'/\blimite\b/i'
1228                    ,'/\bmaquina\b/i','/\bmaquinas\b/i','/\bmaximo\b/i','/\bmaximos\b/i','/\bmecancia\b/i','/\bmedico\b/i','/\bmexico\b/i','/\bmiercoles\b/i','/\bminimo\b/i','/\bminimos\b/i','/\bmusica\b/i'
1229                    ,'/\bnumero\b/i','/\bnumeros\b/i'
1230                    ,'/\bpagina\b/i','/\bpaginas\b/i','/\bpais\b/i','/\bpaises\b/i','/\bparticula\b/i','/\bpelicula\b/i','/\bpie\b/i','/\bpolitica\b/i','/\bportuges\b/i','/\bpublico\b/i'
1231                    ,'/\brazon\b/i','/\bresumen\b/i'
1232                    ,'/\bsabado\b/i'
1233                    ,'/\btecnica\b/i','/\btelefono\b/i','/\btelefonos\b/i','/\btio\b/i','/\btitulo\b/i','/\btitulos\b/i','/\btunel\b/i'
1234                    ,'/\bunica\b/i','/\bunico\b/i','/\bultimo\b/i','/\bultimos\b/i','/\bultima\b/i','/\bultimas\b/i'
1235                    ,'/\busuario\b/i'
1236                    ,'/ion\b/i'
1237                   )
1238            ,array('acción','alemán','almacén','año','años','área','áreas','artículo','artículos'
1239                ,'bitácora','bitácoras'
1240                ,'cálculo','caractér','catálogo','catálogos','código','comentario','compañía','compañias','C.P.'
1241                ,'depósito','depósitos','día','días','dígito','dígitos','dólar','dólares'
1242                ,'economía','electrónica','electrónico','electrónicos','español'
1243                ,'interés','imágenes','índice','índices','inglés'
1244                ,'fábrica','fránces','fotográfica'
1245                ,'gráfica'
1246                ,'huésped'
1247                ,'límite'
1248                ,'máquina','máquinas','máximo','máximos','mecánica','médico','México','miércoles','mínimo','mínimos','música'
1249                ,'número','números'
1250               ,'página','páginas','país','paises','partícula','película','pié','política','portugués','público'
1251               ,'razon','resumen'
1252               ,'sábado'
1253               ,'tecnica','teléfono','teléfonos','tio','título','títulos','túnel'
1254               ,'Única','Único','Último','Últimos','Última','Últimas'
1255               ,'usuario'
1256               ,'ión'
1257            )
1258            ,preg_replace(
1259                array('/^idioma_/','/^kv_/','/^iac_/','/_id$/','/_/')
1260                ,array('','','','',' ')
1261                ,$fieldName ?? ''
1262            )
1263        );
1264        if($ret === 'rfc' || $ret === 'curp' || $ret === 'imss' || $ret === 'cp')
1265            return strtoupper($ret);
1266        if($capWords)
1267            $ret=ucwords($ret ?? '');
1268        if($htmlentities)
1269            return ia_htmlentities( str_replace( array(' De ',' A ',' Del ',' Al ',' El ',' La ',' Las ',' Los '), array(' de ',' a ',' del ', ' al ', ' el ',' la ',' las ',' los '),$ret ) );
1270        else
1271            return str_replace( array(' De ',' A ',' Del ',' Al ',' El ',' La ',' Las ',' Los '), array(' de ',' a ',' del ', ' al ', ' el ',' la ',' las ',' los '),$ret );
1272}
1273
1274
1275
1276/**
1277 * @param int|string $month
1278 * @param int|string $day
1279 * @param int|string $year
1280 * @return false|int
1281 */
1282    function ia_mktime_day($month, $day, $year):false|int {
1283        return mktime(0,0,0,$month,$day,$year);
1284    }
1285
1286function array2Select(
1287    $datos, $key, $valor, $set_val = null, $identifier = 'select', $label = 'Seleccione',
1288    $onlyOptions = false, $addNull = true, $extra_data = [], $extra_html = ['html'=> '', 'keys' => []], $height_personal = null,
1289    $isSelectize = true, $group= null
1290): string
1291{
1292    $class = (!$isSelectize) ? 'notSelectize' : 'selectize';
1293    $select = $onlyOptions ? "" : "<select class='$class' name='$identifier' id='$identifier'>";
1294    if($addNull) {
1295        $select .= "<option value=''>$label</option>";
1296    }
1297    $keysReplace = $extra_html['keys'] ?? [];//'color_valor';
1298
1299    if (!empty($group)){
1300        $datos = objetivisa_($datos, $group);
1301        foreach ($datos as $array_key => $items) {
1302            // dd_("ITEMS", $items);
1303            $options = "";
1304            foreach ($items as  $item) {
1305                $extraData = "";
1306                foreach ($extra_data as $label_extra => $key_extra) {
1307                    $dataLabel = (is_numeric($label_extra)) ? $key_extra: $label_extra;
1308                    $extraData .= " data-$dataLabel='$item[$key_extra]";
1309                }
1310                $item = (array) $item;
1311
1312                $data_extra_html = $extra_html['html'] ?? '';
1313                foreach ($keysReplace as $keyReplace) {
1314                    $strReplace = "{@$keyReplace}";
1315                    $data_extra_html = str_replace($strReplace, $item[$keyReplace], $data_extra_html);
1316                }
1317                if (!empty($data_extra_html)) {
1318                    $data_extra_html = " data-extra_html='$data_extra_html";
1319                }
1320                $data_height_personal = '';
1321                if (!empty($height_personal)) {
1322                    $data_height_personal = " data-height_personal='$height_personal";
1323                }
1324                $selected = "";
1325                if (is_array($set_val)) {
1326                    if (in_array($item[$key], $set_val)) {
1327                        $selected = "selected='selected'";
1328                    }
1329                } else {
1330                    $selected = (string)$item[$key] === (string)$set_val ? "selected='selected'" : "";
1331                }
1332                $options .= "<option value='".ia_htmlentities($item[$key])."$selected $extraData $data_extra_html $data_height_personal>".ia_htmlentities($item[$valor])."</option>";
1333            }
1334            $label = strtoupper($array_key);
1335            $select .= "<optgroup label='$label'>$options</optgroup>";
1336        }
1337    }
1338    else {
1339        foreach ($datos as  $item) {
1340            $extraData = "";
1341            foreach ($extra_data as $label_extra => $key_extra) {
1342                $dataLabel = (is_numeric($label_extra)) ? $key_extra: $label_extra;
1343                $extraData .= " data-$dataLabel='$item[$key_extra]";
1344            }
1345            $item = (array) $item;
1346
1347            $data_extra_html = $extra_html['html'] ?? '';
1348            foreach ($keysReplace as $keyReplace) {
1349                $strReplace = "{@$keyReplace}";
1350                $data_extra_html = str_replace($strReplace, $item[$keyReplace], $data_extra_html);
1351            }
1352            if (!empty($data_extra_html)) {
1353                $data_extra_html = " data-extra_html='$data_extra_html";
1354            }
1355            $data_height_personal = '';
1356            if (!empty($height_personal)) {
1357                $data_height_personal = " data-height_personal='$height_personal";
1358            }
1359            $selected = "";
1360            if (is_array($set_val)) {
1361                if (in_array($item[$key], $set_val)) {
1362                    $selected = "selected='selected'";
1363                }
1364            } else {
1365                $selected = (string)$item[$key] === (string)$set_val ? "selected='selected'" : "";
1366            }
1367
1368            $select .= "<option value='".ia_htmlentities($item[$key])."$selected $extraData $data_extra_html $data_height_personal >" . ia_htmlentities($item[$valor])." </option>";
1369        }
1370    }
1371    return $onlyOptions ? $select : "$select</select>";
1372
1373}
1374
1375function colores($lang = 'es'):array
1376{
1377    $colores = [
1378        'blue' =>    'azul',
1379        'green' =>    'verde',
1380        'red' =>    'rojo',
1381        'orange' =>    'naranja',
1382        'yellow' =>    'amarillo',
1383        'pink' =>    'rosa',
1384        'purple' =>  'morado',
1385        'black' =>    'negro',
1386        'white' =>    'blanco',
1387        'grey' =>   'gris',
1388        'gray' =>     'gris',
1389        'brown' =>    'marrón',
1390        'violet' =>    'violeta',
1391    ];
1392    if ($lang === 'es') {
1393        $colores = array_flip($colores);
1394    }
1395    return $colores;
1396}
1397
1398#[NoReturn]
1399function die_Script()
1400{
1401    echo "<script>$(document).ready( function() { setTimeout(function() { $('#loadingMask').fadeOut(0); }, 10); });</script>";
1402    ia_errores_a_dime();
1403    die();
1404}
1405
1406function json_encode_ex($array, $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE):string {
1407    $var = json_encode($array, $flags);
1408    preg_match_all('/(\"function.*?\")|(\"on_.*?\")|(\"\$d.*?\")|(\"colFmt.*?\")|(\"ia.*?\")/', $var, $matches);
1409    foreach ($matches[0] as  $value) {
1410        $newval = str_replace(array('\n', '\t','\/'), array(PHP_EOL,"\t",'/'), trim($value, '"'));
1411        $var = str_replace($value, $newval, $var);
1412    }
1413    return $var;
1414}
1415
1416/**
1417 * @param object|null $app
1418 * @param string $default
1419 * @param array $groupingOrder En que orden y que agrupaciones poner
1420 * @param string $labelPara
1421 * @param string $prefijo
1422 * @noinspection PhpUnusedParameterInspection
1423 */
1424function table_jqgrid_existencias(object|null $app = null, string $default = 'group_ProductoColorBodega', array $groupingOrder = [],
1425                                  string $labelPara = '', string $prefijo = '')
1426{
1427
1428    $existencias = new Existencias($app, $default,
1429        empty($groupingOrder) ? [$default] : $groupingOrder);
1430    $existencia = $existencias->getThisExistenciaList();
1431
1432    if(!empty($groupingOrder))
1433        $existencia['groups'] = array_intersect_key($existencia['groups'], array_flip($groupingOrder));
1434    $selectGroups =
1435        "<label for='groups_existencias'>con: </label>
1436        <select id='groups_existencias' class='notSelectize' style='cursor:pointer' onchange='setNewGrouping(this.value)'>".
1437            array2Select(datos: $existencia['groups'], key: 'value_group', valor: 'label', set_val: $default, onlyOptions: true, addNull: false)
1438        ."</select>
1439        <img style='cursor:pointer; margin-left: 2em;height:24px;width: 24px;border:0 silver solid;' src='../img/expandOne.png' alt='Expand one' title='Expand one' onclick='iaJqGridGrouping.expandOne(gridhandlerExistencia)' id='expand_one_existencias'>
1440        <img style='cursor:pointer; margin-left: 2em;height:32px; width:32px;border:0 silver solid;' src='../img/collapseAll.png' alt='Collapse all' title='Collapse all' onclick='iaJqGridGrouping.collapseAll(gridhandlerExistencia)' id='collapse_all_existencias'> 
1441        <img style='cursor:pointer; margin-left: 2em;height:32px; width:32px ;border:0 silver solid;' src='../img/expandAll.png' alt='Expand all' title='Expand all' onclick='iaJqGridGrouping.expandAll(gridhandlerExistencia)' id='expand_all_existencias'> 
1442        <style>
1443        img:hover{
1444            opacity: .5;
1445            overflow:visible;
1446            border:0 solid rgba(0,0,0,0.7);
1447            box-sizing:border-box;
1448            transition: all 0.4s ease-in-out; }
1449        </style>";
1450
1451    echo "<fieldset class='lblgrp'><legend style='vertical-align: top;'>&nbsp;Existencia $labelPara $selectGroups</legend>";
1452    include_once (dirname(__DIR__).'/bodega/componentes/jqgrid_existencias_list.php');
1453    echo "</fieldset>";
1454}
1455
1456
1457/** @noinspection PhpUnusedParameterInspection */
1458function table_productos_remate($params = [])
1459{
1460    /*$queryProductos = "SELECT
1461                    producto_general_id, producto, en_remate, es_saldo, min_price,
1462                    producto value, producto label, producto_general_id real_value
1463                   FROM producto_general WHERE activo = 'Si' ORDER BY producto";
1464    $productos = ia_sqlArray($queryProductos, 'producto_general_id');
1465    $queryColores = "SELECT
1466                    pc.producto_general_id pg_id, pc.producto_color_id, pc.producto_general_id, pc.color_id, c.color, c.color_valor,
1467                    pc.en_remate, pc.es_saldo
1468                FROM producto_color pc
1469                JOIN color c USING(color_id) ORDER BY c.orden, c.color";
1470    $colores = ia_sqlSelectMultiKey($queryColores, 2);*/
1471    return include_once (dirname(__DIR__).'/bodega/componentes/table_productos_remate_list.php');
1472}
1473
1474/**
1475 * Cambia keys de $array ['producto_id'=>3, 'color'=>4], $newKeys=['color'=>'color.color'] regresa ['producto_id'=>3, 'color.color'=>4]
1476 *
1477 * @param array $array ie ['producto_id'=>3, 'color'=>4]
1478 * @param array $newKeys ie ['color'=>'color.color']
1479 * @return array ie ['producto_id'=>3, 'color.color'=>4]
1480 */
1481function keyRemap(array $array, array $newKeys):array {
1482    $reMapped = [];
1483    foreach($array as $k => $v)
1484        $reMapped[ $newKeys[$k] ?? $k] = $v;
1485    return $reMapped;
1486}
1487
1488/**
1489 * Regresa el label para bodegas.
1490 * @param array $bodega - [bodega => 'verde', label_bodega|label=> 'Clavel', bodega_id=> 'abcd1278y', color|bodega_color=> '##008000'].
1491 * @param array $extraSettings - [returnColor=> true|false, returnNane=> true|false, returnLabel=> true|false, isAnchor=> true|false, urlAnchor: '../../../', titleAnchor: 'titulo de ancla'].
1492 *      - returnColor: Para regresar indicador de color.
1493 *      - returnNane: Para regresar el nombre de la bodega.
1494 *      - returnLabel: Para regresar la clave de la bodega.
1495 *      - isAnchor: Para indicar que es un anchor (<a href=link a la bodega></a>).
1496 *      - urlAnchor: Para indicar la url del anchor.
1497 */
1498function getLabelBodega(array $bodega = [], array $extraSettings = []): string
1499{
1500    if (!is_array($bodega) || count(array_keys($bodega)) == 0) return '';
1501
1502    $settings = array_merge(
1503        ['returnColor' => true, 'returnNane' => true, 'returnLabel' => true, 'isAnchor' => false, 'urlAnchor' => null, 'titleAnchor' => null, 'IsHtml' => true,
1504        'classes' => ''
1505        ],
1506        $extraSettings)
1507    ;
1508
1509    $color = $bodega['bodega_color'] ?? $bodega['color'] ?? '#FFF';
1510    $sectionColor = $settings['returnColor'] ? "<i style='background-color:$color; width: 1em; height: 1em; display: inline-block;margin-right: 5px; border: 1px solid black;'></i>":'';
1511    $sectionName = $settings['returnNane'] ? $bodega['bodega'] ?? '': '';
1512    $label = $bodega['label_bodega'] ?? $bodega['label'] ?? '';
1513    if($label !== '.' && $label !== '')
1514        $sectionLabel = $settings['returnLabel'] ? "<i style='font-weight:100;font-size:smaller'>($label)</i>": '';
1515    else
1516        $sectionLabel = '';
1517
1518    if(empty($settings['grupo']))
1519        $grupo = '';
1520    else
1521        $grupo = empty($bodega['grupo']) ? '' : ' <i style="font-size:0.7em;font-weight: 100">' . ia_htmlentities($bodega['grupo']) . '</i> ';
1522
1523    if (!$settings['IsHtml']) {
1524        return "$sectionName ($label)";
1525    }
1526
1527    $clases = '';
1528    if (!empty($settings['classes']))
1529        $clases = $settings['classes'];
1530
1531    if (!$settings['isAnchor'])
1532        return "<span class='$clases'>$sectionColor $sectionName $sectionLabel$grupo</span>";
1533
1534    if (empty($settings['urlAnchor']))
1535        $settings['urlAnchor'] = getUrlBase()."backoffice/bodega.php?id=$bodega[bodega_id]&iagridvar=iajqgridbodega_var&iah=e";
1536
1537    $titleAnchor = '';
1538    if (!empty($settings['titleAnchor']))
1539        $titleAnchor = "title='" . ia_htmlentities($settings['titleAnchor']) . "'";
1540
1541
1542    return "<a href='$settings[urlAnchor]$titleAnchor><span class='$clases'>$sectionColor $sectionName $sectionLabel</span></a>$grupo";
1543}
1544
1545function getUrlBase(): string
1546{
1547    if (isset($_SERVER["HTTPS"]))
1548        $url_base = $_SERVER["HTTPS"] === 'on' ? 'https': 'http';
1549    else
1550        $url_base = 'http';
1551
1552    $url_base .= "://";
1553    if ($_SERVER["SERVER_PORT"] != "80") {
1554        $url_base .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"]."/".explode("/", $_SERVER["REQUEST_URI"])[1];
1555    } else {
1556        $url_base .= $_SERVER["SERVER_NAME"]."/".explode("/", $_SERVER["REQUEST_URI"])[1];
1557    }
1558    return $url_base . "/";
1559}
1560function getUrlTo($location = '')
1561{
1562    return getUrlBase() . $location;
1563}
1564/**
1565 * Elimina los items de un array
1566 * @param array $array
1567 * @param string|array $value
1568 * @return array
1569 */
1570function unsetItem(array $array, string|array $value): array
1571{
1572    if (is_array($value)) {
1573        foreach ($value as $v) {
1574            if (isset($array[$v]))
1575                unset($array[$v]);
1576        }
1577    } else {
1578        unset($array[$value]);
1579    }
1580    return $array;
1581}
1582
1583/**
1584 * @param array $array
1585 * @param string $on
1586 * @param int $order
1587 * @return array
1588 */
1589function array_sort(array $array, string $on, $order=SORT_ASC): array
1590{
1591    $new_array = array();
1592    $sortable_array = array();
1593
1594    if (count($array) > 0) {
1595        foreach ($array as $k => $v) {
1596            if (is_array($v)) {
1597                foreach ($v as $k2 => $v2) {
1598                    if ($k2 == $on) {
1599                        $sortable_array[$k] = $v2;
1600                    }
1601                }
1602            } else {
1603                $sortable_array[$k] = $v;
1604            }
1605        }
1606
1607        switch ($order) {
1608            case SORT_ASC:
1609                asort($sortable_array);
1610                break;
1611            case SORT_DESC:
1612                arsort($sortable_array);
1613                break;
1614        }
1615
1616        foreach ($sortable_array as $k => $v) {
1617            $new_array[$k] = $array[$k];
1618        }
1619    }
1620
1621    return $new_array;
1622}
1623
1624function getEnums(string $table, string $field)
1625{
1626    $method = __FUNCTION__;
1627    /** @noinspection SqlResolve */
1628    $enum_value =
1629        ia_singleread("SELECT /*$method*/ REPLACE(REPLACE(SUBSTRING(COLUMN_TYPE,7), \"'\", ''), ')', '') values_ FROM information_schema.COLUMNS WHERE TABLE_NAME=".strit($table)." AND COLUMN_NAME=".strit($field));
1630
1631    if (empty($enum_value))
1632        return [];
1633
1634    $enums = explode(",", $enum_value);
1635    return array_combine($enums, $enums);
1636}
1637
1638function adjustBrightness($hex, $steps) {
1639
1640    $colorsDefault = [
1641        'blue' => "#0000FF",
1642        'azul' => "#0000FF",
1643        'green' => '#008000',
1644        'verde' => '#008000',
1645        'red' => '#FF0000',
1646        'rojo' => '#FF0000',
1647        'orange' => '#FFA500',
1648        'naranja' => '#FFA500',
1649        'yellow' => '#FFFF00',
1650        'amarillo' => '#FFFF00',
1651        'pink' => '#FFC0CB',
1652        'rosa' => '#FFC0CB',
1653        'purple' => '#800080',
1654        'morado' => '#800080',
1655        'black' => '#000000',
1656        'negro' => '#000000',
1657        'white' => '#FFFFFF',
1658        'blanco' => '#FFFFFF',
1659        'gray' => '#808080',
1660        'grey' => '#808080',
1661        'gris' => '#808080',
1662        'brown' => '#A52A2A',
1663        'cafe' => '#A52A2A',
1664        'maroon' => '#800000',
1665        'marrón' => '#800000',
1666        'violet' => '#EE82EE',
1667        'violeta' => '#EE82EE',
1668    ];
1669
1670    $hex = strtolower($hex);
1671
1672    $hex = $colorsDefault[$hex] ?? $hex;
1673
1674    // Steps should be between -255 and 255. Negative = darker, positive = lighter
1675    $steps = max(-255, min(255, $steps));
1676
1677    // Normalize into a six character long hex string
1678    $hex = str_replace('#', '', $hex);
1679    if (strlen($hex) == 3) {
1680        $hex = str_repeat(substr($hex,0,1), 2).str_repeat(substr($hex,1,1), 2).str_repeat(substr($hex,2,1), 2);
1681    }
1682
1683    // Split into three parts: R, G and B
1684    $color_parts = str_split($hex, 2);
1685    $return = '#';
1686
1687    foreach ($color_parts as $color) {
1688        $color   = hexdec($color); // Convert to decimal
1689        $color   = max(0,min(255,$color + $steps)); // Adjust color
1690        $return .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT); // Make two char hex code
1691    }
1692
1693    return $return;
1694}
1695
1696function limpiaDatos(array $datos):array {
1697    // dd_($movimiento);
1698    // $exclude_case_upper = ['cp_cliente_direccion_id_destino', 'cliente'];
1699    foreach ($datos as $key => &$value) {
1700        $partes = explode("_", $key);
1701        if ($partes[count($partes)-1] == 'id')
1702            continue;
1703
1704        if(array_key_exists($key,['quantity'=>'', 'rollos'=>''] )) {
1705            $value = str_replace(',', '', strim($value));
1706            continue;
1707        }
1708        if(!is_array($value)) {
1709            $value = strim($value);
1710            // $value = mb_convert_case(strim($value), MB_CASE_UPPER);
1711        }
1712    }
1713    return $datos;
1714}
1715
1716
1717function searchValueInArrayByColumn($value, $array, $column)
1718{
1719    $index = array_search($value, array_column($array, $column));
1720
1721    if ($index !== false)
1722        return $array[$index];
1723
1724    return [];
1725
1726}
1727
1728function get_url_origin($s = null, $use_forwarded_host=false) {
1729    $s = ($s == null) ? $_SERVER: $s;
1730    $ssl = ( ! empty($s['HTTPS']) && $s['HTTPS'] == 'on' ) ? true:false;
1731    $sp = strtolower( $s['SERVER_PROTOCOL'] );
1732    $protocol = substr( $sp, 0, strpos( $sp, '/'  )) . ( ( $ssl ) ? 's' : '' );
1733
1734    $port = $s['SERVER_PORT'];
1735    $port = ( ( ! $ssl && $port == '80' ) || ( $ssl && $port=='443' ) ) ? '' : ':' . $port;
1736
1737    $host = ( $use_forwarded_host && isset( $s['HTTP_X_FORWARDED_HOST'] ) ) ? $s['HTTP_X_FORWARDED_HOST'] : ( isset( $s['HTTP_HOST'] ) ? $s['HTTP_HOST'] : null );
1738    $host = isset( $host ) ? $host : $s['SERVER_NAME'] . $port;
1739
1740    return $protocol . '://' . $host;
1741
1742}
1743
1744function get_full_url( $s = null, $use_forwarded_host=false ) {
1745    $s = ($s == null) ? $_SERVER: $s;
1746    return get_url_origin( $s, $use_forwarded_host ) . $s['REQUEST_URI'];
1747}
1748
1749function get_url_out_params($url = '')
1750{
1751    $url = empty($url) ? get_full_url(): $url;
1752
1753    $parts = explode("?",$url);
1754    return $parts[0] ;
1755}
1756
1757
1758function notasFaltantes ($bodega_id)
1759{
1760
1761}
1762
1763
1764/** @noinspection PhpRedundantOptionalArgumentInspection */
1765function bodegasResumenNumeroNotas(array $bodegas, array $bodegasHacerMovimientos, int &$totalFaltan, $con_label = true):string {
1766    $ret = ''; $minNumNota = 0; $totalFaltan = 0;
1767    foreach($bodegas as $bodega_id => $b) {
1768        if( ($b['activo'] ?? 'Si') === 'No' )
1769            continue;
1770
1771        $gaps = NotaBodega::numerosDeNotasFaltantes($bodega_id, $minNumNota, false, false);
1772        $maxNumber = ia_singleread("SELECT MAX(numero_as_num) FROM nota_bodega WHERE bodega_id=" . strit($bodega_id));
1773        $maxNumber == '' ? 1 : (int)$maxNumber + 1;
1774        if($maxNumber == '')
1775            $maxNumber = 1;
1776
1777        $lastNoteFormatted = number_format($maxNumber, 0, '', ',');
1778        //$newNumberFormatted = number_format($newNumber, 0, '', ',');
1779        $numFaltan = count($gaps);
1780        if($numFaltan === 0) {
1781            $missingCount = ' Faltan: 0  # de notas';
1782            //$displayGaps = '';
1783        } else {
1784            $totalFaltan += $numFaltan;
1785            if($numFaltan > 30) {
1786                $gaps = NotaBodega::numerosDeNotasFaltantes($bodega_id, $minNumNota, true, false);
1787                //$len = count($gaps) - 1;
1788                //echo "<li>len=$len gaps=" . print_r($gaps, true);
1789                //$gaps[$len] = $gaps[$len] -2;
1790                array_walk($gaps ,
1791                    function(&$n){
1792                        $n = number_format($n, 0, '', ',');
1793                    }
1794                );
1795                $displayGaps = implode(" al ", $gaps );
1796
1797            } else {
1798                array_walk($gaps ,
1799                    function(&$n){
1800                        $n = number_format($n, 0, '', ',');
1801                    }
1802                );
1803                $displayGaps = implode("; ", $gaps);
1804            }
1805            if($numFaltan === 1) {
1806                $laS = $laN = '';
1807            } else {
1808                $laS = 's'; $laN = 'n';
1809            }
1810            $missingCount =
1811                "<span class='bodega_boxResumenShadow' style='color:red;font-weight: 800'>Falta$laN " . number_format($numFaltan, 0, '', ',') . " nota$laS" .
1812                " <span style='color:red;font-weight: 800;font-size:2em;float:right'>&cross;</span>" .
1813                "<p style='clear:both;padding-left:2em;text-overflow: ellipsis; overflow: hidden;max-width:500px;max-height:4em;color:red;font-weight: 800'>$displayGaps</span>" .
1814                "</span>";
1815        }
1816
1817        $label = "<span style='cursor:pointer;' title='Ver última nota' onclick='dialogConsultarExistencia.consulta_ultima_nota(\"$bodega_id\")'>" . getLabelBodega($b) . "</span> ";
1818        if(array_key_exists($bodega_id, $bodegasHacerMovimientos)) {
1819            $link = "<a style='background:none!important;text-decoration:underline;color:blue;font-weight:bold;' href='../bodega/alta_movimiento.php?bodega_id=$b[bodega_id]&movimientode";
1820            $label .=  "$link=entrada'>Entrada</a> $link=salida'>Salida</a>";
1821        }
1822
1823        $ret .=
1824            "<li><div style='white-space: pre;width:fit-content'>" . $label . "</div>" .
1825            " <span class='bodega_boxResumenShadow'  style='color:black'>Last Note: $lastNoteFormatted  $missingCount</span>";
1826    }
1827    return $ret;
1828}
1829
1830function miniLog(array $values):string {
1831    $m='';
1832    if(array_key_exists('iac_edits',$values) ) {
1833        $m.="<b>Cambios:</b> <i>".number_format($values['iac_edits'], 0, '', ',')."</i>";
1834    }
1835    if( array_key_exists('ultimo_cambio',$values) || array_key_exists('ultimo_cambio_por',$values)
1836    ) {
1837        if($m!='')
1838            $m.='. ';
1839        $m.="<b>Último cambio</b>";
1840        if(array_key_exists('ultimo_cambio',$values) )
1841            $m.=" el: <i>".mysqlDate2display($values['ultimo_cambio']) . " (" . fechaDiff(Date('Y-m-d H:i:s'), $values['ultimo_cambio']) . ")</i>";
1842        if(array_key_exists('ultimo_cambio_por',$values) )
1843            $m.=' por: <i>'.$values['ultimo_cambio_por']."</i>";
1844
1845
1846        if(!empty($values['modificacion_importante_el']) && $values['modificacion_importante_el'] !== '0000-00-00 00:00:00')
1847            $m.=" <span style='color:red'>Mod Imp el: <i>".mysqlDate2display($values['modificacion_importante_el']) . " (" . fechaDiff(Date('Y-m-d H:i:s'), $values['modificacion_importante_el']) . ")</i></span>";
1848        if(!empty($values['modificacion_importante_por']) )
1849            $m.=' por: <i>'.$values['modificacion_importante_por']."</i>";
1850
1851        if(array_key_exists('iac_last_edit_ip',$values) )
1852            $m.=' IP: <i>'.$values['iac_last_edit_ip']."</i>";
1853    }
1854    if(array_key_exists('alta_db',$values) || array_key_exists('alta_por',$values) ) {
1855        if($m!='')
1856            $m.='. | ';
1857        $m.=" <b>Alta</b>";
1858        if(array_key_exists('alta_db',$values) )
1859            $m.=" el: <i>".mysqlDate2display($values['alta_db']) . " (" . fechaDiff(Date('Y-m-d H:i:s'), $values['alta_db']) . ")</i>";
1860        if(array_key_exists('alta_por',$values) )
1861            $m.=' por <i>'.$values['alta_por']."</i>";
1862    }
1863    return $m;
1864}
1865
1866/**
1867 * Redondea y pone comas a un numero en string, number_format para strings
1868 *
1869 * @param string|int|float|null|bool $num
1870 * @param int $decimals
1871 * @return string el $num con comas y redondeado al $decimals decimales
1872 */
1873function bcformat(string|int|float|null|bool $num, int $decimals=2):string {
1874    if($num === null || $num === '')
1875        return '';
1876    if(!is_numeric($num))
1877        return $num;
1878    $num = bcadd("0", numeric2string($num), $decimals);
1879    $int = strstr($num, '.', true);
1880    if($int === false) {
1881        $int = $num;
1882        $frac = '';
1883    } else {
1884        $frac = strstr($num, '.');
1885    }
1886    return preg_replace('/(\d)(?=(\d{3})+(?!\d))/mS', '$1,', $int) . $frac;
1887}
1888
1889function numeric2string($number) {
1890    if(is_array($number)) {
1891        foreach($number as &$n)
1892            $n = numeric2string($n);
1893        return $number;
1894    }
1895    if(empty($number))
1896        return "0";
1897    if(!is_numeric($number) || stripos("$number", 'E') === false)
1898        return "$number";
1899    return rtrim(rtrim(bcmul(sprintf("%.20f", $number ), '1', 20), '0'), '.');
1900}
1901
1902/**
1903 * Abs para bc math
1904 *
1905 * @param string $num
1906 * @param int $decimals
1907 * @return string
1908 */
1909function bcabs($num, $decimals):string {
1910    return bccomp("0", $num, $decimals) >= 0 ?
1911        bcmul("-1", $num, $decimals) :
1912        $num;
1913}
1914
1915/**
1916 * Pasa la existencia de un producto-color de una bodega a cero, revisando permisos.
1917 *
1918 * @param string $producto_bodega_id
1919 * @return string|array empty ok producto fue reseted
1920 */
1921function producto_color_reset($producto_bodega_id, $execute = true, $solo_qty = false, &$existencia_return = []):string|array {
1922    // Valida Permisos del usuario
1923    if(!usuarioTipoRony() && !usuarioSupervisaBodega() && !puedePermisoUsuario(nombrePermiso: 'puede_hacer_reset_producto_color')) {
1924        return "SIN PERMISO";
1925    }
1926    // Obtén bodega-producto-color a pasar a ceros
1927    $method = __FUNCTION__;
1928    $sql = "SELECT /*$method*/ pb.producto_bodega_id, pb.bodega_id, pb.producto_general_id, pb.color_id, 
1929            pb.existencia_rollos, pb.existencia_quantity,
1930            pb.existencia_rollos as rollos, pb.existencia_quantity as quantity,
1931           pc.en_remate, pc.es_saldo, pc.lento, pc.super_lento,
1932           IF(existencia_rollos < 0 || existencia_quantity < 0, 'Entrada', 'Salida') as entrada_salida,
1933            (SELECT origen_bodega_id FROM origen_bodega WHERE bodega_id = pb.bodega_id) AS origen_id,
1934           -- '' as origen_id,
1935           'Correccion' as es, 'Correccion' as tipo
1936            FROM producto_bodega pb 
1937                LEFT OUTER JOIN producto_color pc on 
1938                    pb.producto_general_id = pc.producto_general_id AND pb.color_id=pc.color_id
1939            WHERE pb.producto_bodega_id = " .strit($producto_bodega_id);
1940
1941    $existencia = ia_singleton($sql);
1942    if(empty($existencia)) {
1943        return "NOT FOUND";
1944    }
1945    if(empty($existencia['en_remate']))
1946        $existencia['en_remate'] = $existencia['es_saldo'] = $existencia['lento'] = $existencia['super_lento'] = 'No';
1947    $existencia_return['existencia_quantity'] = $existencia['existencia_quantity'];
1948    $existencia_return['existencia_rollos'] = $existencia['existencia_rollos'];
1949
1950    global $gIAParametros;
1951    $maxResetRollos = $gIAParametros['reset_max_rollos'] ?? "1";
1952    $maxResetQuantity = $gIAParametros['reset_max_quantity'] ?? "5.00";
1953
1954    if(!usuarioTipoRony()) {
1955        // Si la existencia de rollos es mayor a $maxResetRollos
1956        if(bccomp(bcabs($existencia['rollos'], 0), $maxResetRollos, 0) > 0)
1957            return "INVALID ROLLS";
1958        // Si la existencia de quantity es mayor a $maxResetQuantity
1959        if(bccomp(bcabs($existencia['quantity'], 2), $maxResetQuantity, 2) > 0)
1960            return "INVALID QUANTITY";
1961    }
1962
1963
1964    $query[] = "INSERT INTO reset_history(bodega_id,producto_general_id,color_id,rollos,quantity,alta_por)
1965                    VALUES ('{$existencia['bodega_id']}','{$existencia['producto_general_id']}','{$existencia['color_id']}',
1966                            '{$existencia['rollos']}','{$existencia['quantity']}','{$_SESSION['usuario']}')";
1967
1968    // Pasa existencia en bodega del producto-color a ceros
1969    $set_campos = ["existencia_quantity" => '0.00', 'existencia_rollos' => '0.00'];
1970    if ($solo_qty)
1971        $set_campos = ["existencia_quantity" => '0.00'];
1972
1973    $set_campos['ultimo_cambio'] = "NOW()";
1974    $set_campos['ultimo_cambio_por'] = $_SESSION['usuario'];
1975    $sqlBuilder = new Iac\inc\sql\IacSqlBuilder();
1976    $query[] = $sqlBuilder->update('producto_bodega', $set_campos, ['producto_bodega_id' => $producto_bodega_id], comment:$method);
1977
1978    $existencia['rollos'] = $solo_qty ? "0" : bcmul($existencia['rollos'], "-1", 0);
1979    $existencia['quantity'] = bcmul($existencia['quantity'], "-1", 2);
1980    $existencia['fecha'] = Date('Y-m-d');
1981    $existencia['entrada_salida'] = 'Entrada';
1982    $existencia['tipo'] = 'Entrada';
1983    $existencia['origen_id'] = AJUSTE_ORIGEN_ID;
1984
1985    $bodegaExistenciaDiaria = new BodegaExistenciaDiaria(false);
1986    $resetItems = $bodegaExistenciaDiaria->sqlResetItem($existencia);
1987    if(is_array($resetItems))
1988        $query = [...$query,  ...$resetItems];
1989
1990    if (!$execute)
1991        return $query;
1992    return ia_transaction($query) ? "DB ERROR" : "";
1993}
1994
1995function me_pregunto_notas_bodega() {
1996    $method = __FUNCTION__;
1997    $return = [];
1998    $dias = 7;
1999    $devolucionesSql =
2000        "select /*$method*/ tipo, count(*) as 'num', count(*) * 100.0 / sum(count(*))  over() as 'avg'
2001    from nota_bodega
2002    WHERE tipo IN ('movimiento', 'devolucion', 'VENTA CLIENTE') AND fecha >= DATE_SUB(CURDATE(), INTERVAL $dias DAY)
2003    GROUP BY 1";
2004    $devoluciones = ia_sqlArray($devolucionesSql, 'tipo');
2005    if(!empty($devoluciones)) {
2006        if(array_key_exists('Devolucion', $devoluciones)) {
2007            if($devoluciones['Devolucion']['avg'] >= 0.5)
2008                $return[] = "<b class='bb'>" . $devoluciones['Devolucion']['num'] . " devoluciones</b> en los últimos $dias días, creo <b class='bb'>¿son muchas?</b>";
2009        } else {
2010            $return[] = "¿<b class='bb'>Sin devoluciones</b> en los últimos $dias días?";
2011        }
2012    }
2013
2014    $productosVendidosSql =
2015        "SELECT /*$method*/ count(DISTINCT producto_general_id) 
2016         FROM nota_bodega 
2017         WHERE tipo IN ('movimiento', 'VENTA CLIENTE') AND fecha >= DATE_SUB(CURDATE(), INTERVAL $dias DAY)";
2018    $productosVendidos = ia_singleread($productosVendidosSql, 0);
2019    if($productosVendidos !== false && $productosVendidos < 35)
2020        $return[] = "<b class='bb'>$productosVendidos productos vendidos</b> en los últimos $dias días, <b class='bb'>¿son pocos. no?</b>";
2021    $edicionesImportantesSql =
2022        "SELECT /*$method*/ b.bodega, COUNT(*)
2023    from nota_bodega_autorizacion nba LEFT OUTER JOIN bodega b ON b.bodega_id=nba.bodega_id
2024    WHERE nba.ultimo_movimiento='modificacion'AND nba.ultimo_cambio  > DATE_SUB(CURDATE(), INTERVAL $dias DAY) 
2025    GROUP BY 1
2026    HAVING COUNT(*)>5";
2027    $edicionesImportantes = ia_sqlKeyValue($edicionesImportantesSql);
2028    if(!empty($edicionesImportantes))
2029        foreach($edicionesImportantes as $bodega => $count)
2030            $return[] = "En la bodega <b class='bb'>$bodega</b> se realizaron <b class='bb'>$count modificaciones importantes</b> en los últimos $dias días, <b class='bb'>¿No son muchas?</b>";
2031
2032
2033    return empty($return) ? "" :
2034        "<style>.light{font-weight: 100;}.bb{font-weight:800}</style><div class='light' style='color:red;font-family: Arial, sans-serif'><span style='color:red;'>Hmmm de las <b>notas de bodega</b>, me pregunto:</span><dl><dt>" .  implode("<dt>", $return) . "</dl></div>"
2035        ."(solo en desarrollo existo)";
2036}
2037
2038function ia_htmlentities($s="",$forza=false,$hardblank=false) {
2039    if($hardblank && empty($s) ) return '&nbsp;';
2040    if(is_array($s)) {
2041        echo "<div style='clear:both'><hr />ia_htmlentites got array=<pre>".print_r($s,true)."<p>trace<p>";
2042        debug_print_backtrace();
2043
2044        echo "</pre><hr></div>";
2045    }
2046    if($s === null)
2047        $s = '';
2048    return htmlentities($s,ENT_QUOTES | ENT_HTML401 | ENT_SUBSTITUTE | ENT_DISALLOWED,IACHARSET);
2049}
2050
2051function mysqlDate2display($date, $full_time=false) {
2052    global $gIAData;
2053    if(empty($date) || $date === '0000-00-00')
2054        return '';
2055    if(strlen($date) === 19)
2056        return mysqlDateTime2display($date, $full_time);
2057
2058    //VCA
2059    $tmp=explode('-',$date);
2060    if(sizeof($tmp) !==3 ) return '';
2061    if($tmp[2] > 35)
2062        $date = "$tmp[2]-$tmp[1]-$tmp[0]";
2063    if(!checkdate((int)$tmp[1],(int)$tmp[2],(int)$tmp[0]))
2064        $date = date('Y-m-d');
2065
2066    try {
2067        return substr($date,8,2).'/'.$gIAData['monthShrot'][substr($date,5,2)+0].'/'.substr($date,2,2);
2068    } catch(Exception) { return $date; }
2069}
2070
2071function mysqlDateTime2display($date, $full_time=false) {
2072    global $gIAData;
2073    if(empty($date))
2074        return '';
2075    if(strlen($date) === 19)
2076        try {
2077            return substr($date,8,2).'/'
2078                .$gIAData['monthShrot'][substr($date,5,2)+0]
2079                .'/'.substr($date,2,2).' '
2080                .($full_time?substr($date,10):substr($date,10,6));
2081        } catch(Exception) { return $date; }
2082    return $date;
2083}
2084
2085function mysqlTimestamp2display($timestamp, $full_time=false) { return mysqlDateTime2display($timestamp, $full_time); }
2086
2087/**
2088 * Regresa el nombre del color_id (color), blanco en not found
2089 */
2090function color_id_nombre(string $color_id):string {
2091    static $coloresCache;
2092    if(empty($coloresCache)) {
2093        $function = __FUNCTION__;
2094        $coloresCache = ia_sqlKeyValue("SELECT /*$function*/ color_id, color FROM color");
2095    }
2096    return $coloresCache[$color_id] ?? '';
2097}
2098/**
2099 * Regresa true si esta activo el color, false inactivo o not found
2100 */
2101function color_id_activo(string $color_id):bool {
2102    static $coloresCache;
2103    if(empty($coloresCache)) {
2104        $function = __FUNCTION__;
2105        $coloresCache = ia_sqlKeyValue("SELECT /*$function*/ color_id, activo FROM color");
2106    }
2107    return $coloresCache[$color_id] ?? '' === 'Si';
2108}
2109/**
2110 * Regresa el nombre del producto_general_id (producto), blanco en not found
2111 */
2112function producto_general_id_nombre($producto_general_id):string {
2113    static $productosCache;
2114    if(empty($productosCache)) {
2115        $function = __FUNCTION__;
2116        $productosCache = ia_sqlKeyValue("SELECT /*$function*/ producto_general_id, producto FROM producto_general");
2117    }
2118    return $productosCache[$producto_general_id] ?? '';
2119}
2120/**
2121 * Regresa true si esta activo el producto, false inactivo o not found
2122 */
2123function producto_general_id_activo($producto_general_id):bool {
2124    static $productosCache;
2125    if(empty($productosCache)) {
2126        $function = __FUNCTION__;
2127        $productosCache = ia_sqlKeyValue("SELECT /*$function*/ producto_general_id, activo FROM producto_general");
2128    }
2129    return $productosCache[$producto_general_id] ?? '' === 'Si';
2130}
2131
2132
2133function checked($value, $checkedValues):string {
2134    $val = $value instanceof Stringable ? (string)$value : $value;
2135    $valueTag = " value='" . htmlentities($val) . "' ";
2136    if(is_array($checkedValues))
2137        return $valueTag . (in_array($val, $checkedValues, false) ? "checked='checked' " : " ");
2138    if($checkedValues instanceof Stringable)
2139        return $valueTag . ((string)$checkedValues === $val ? "checked='checked' " : " ");
2140    return $valueTag . ($checkedValues == $value ? "checked='checked' " : " ");
2141}
2142
2143/**
2144 * Returns htmlentity protected value tag, and selectedTag if $value in $selectedValues
2145 *
2146 * @param string|Stringable|int|float|bool|null $value
2147 * @param string|Stringable|array<int|string, string|int|float|bool|null> $selectedValues
2148 * @return string " value='$value' " or " value='$value' selected='selected' "
2149 */
2150function selected($value, $selectedValues):string {
2151    $val = $value instanceof Stringable ? (string)$value : $value;
2152    $valueTag = " value='" . htmlentities((string)$val) . "' ";
2153    if(is_array($selectedValues))
2154        return $valueTag . (in_array($value, $selectedValues, false) ? " selected='selected' " : " ");
2155    if($selectedValues instanceof Stringable)
2156        return $valueTag . ((string)$selectedValues == $val ? " selected='selected' " : " ");
2157    return $valueTag . ($selectedValues == $value ? " selected='selected' " : " ");
2158}
2159
2160function statusBodegaSelect($name, $id) {
2161    global $gIAParametros;
2162    $statusValues = explode(",", $gIAParametros['existencia_default_status']??'');
2163    foreach($statusValues as &$d) {
2164        $d = trim($d);
2165    }
2166    unset($d);
2167    return strim("
2168<style>.option-small{font-size:0.9em!important;font-weight: 100!important;font-family: Courier monospace monospace, monospace}</style>
2169        <select name='$name' id='$id' class='notSelectize multiselect' multiple style='width:17em;'>
2170        <option class='option-small'" . selected('donde', $statusValues) . " >Donde!</option>
2171        <option class='option-small'" . selected('ceros', $statusValues) . " >Ver Ceros</option>
2172        <option class='option-small'" . selected('negativos', $statusValues) . " >Solo Negativos</option>
2173        <option class='option-small'" . selected('sin_existencia', $statusValues) . " >Existencia Cero</option>
2174        <option class='option-small'" . selected('saldo', $statusValues) . " >Saldos</option>
2175        <option class='option-small'" . selected('pedir', $statusValues) . " >Remate</option>
2176        <option class='option-small'" . selected('super_saldo', $statusValues) . " >Dead Stock</option>
2177        <option class='option-small'" . selected('lento', $statusValues) . " >Lento</option>
2178        <option class='option-small'" . selected('super_lento', $statusValues) . " >Super Lento</option>
2179        </select>
2180    ");
2181}
2182
2183/**
2184 * Para buscar por regExp, lento, en un campo json o text que guarda json con like en un $property del json el text $busca
2185 *
2186 * @param string $fieldName
2187 * @param string $property
2188 * @param string $busca
2189 * @return string
2190 */
2191function jsonRegExp($fieldName, $property, $busca):string {
2192    $fieldName = fieldit(trim($fieldName ?? ''));
2193    $property = substr(strit(trim($property ?? '')), 1, -1);
2194    $busca = substr(strit(strim($busca)), 1, -1);
2195    return /** @lang SQL */
2196        "REGEXP_LIKE($fieldName, '.*\"$property\" *: *\"[a-z\\\\s,\\\\.\\\\d\\\\&]*{$busca}[a-z\\\s,\\\.\\\d\\\\&]*\"', 'i')";
2197}
2198
2199function jsonLike(string $tableName, string $fieldName, string $property, string $busca, string $op = 'cn'):string {
2200    switch($op) {
2201        case 'ew':
2202            $start = '%';
2203            $end = '';
2204            break;
2205        case 'bw':
2206            $start = '';
2207            $end = '%';
2208            break;
2209        default:
2210            $start = '%';
2211            $end = '%';
2212    }
2213
2214    $fieldName = fieldit(trim($fieldName ?? ''));
2215    $property = substr(strit(trim($property ?? '')), 1, -1);
2216    $busca = substr(strit(strim($busca)), 1, -1);
2217
2218    return /** lang SQL */
2219        "EXISTS(
2220              SELECT 1
2221              FROM nota_bodega nbj
2222                JOIN JSON_TABLE(nbj.$fieldName, '\$[*]' COLUMNS (
2223                    fue_error VARCHAR(2) PATH '\$.fue_error',
2224                    lbl TEXT PATH '\$.$property'
2225                )) AS j
2226              WHERE nota_bodega.nota_bodega_id=nbj.nota_bodega_id AND j.fue_error='No' AND j.lbl LIKE '$start{$busca}$end'
2227          )";
2228}
2229
2230function strlike($str):string {
2231 return str_replace(array('%', '_'), array("\\%", "\\_"), $str ?? '');
2232}
2233
2234
2235/**
2236 * si $tableName.$fieldName se guarda como json da el property que desplegar/buscar. blanco no es json
2237 * Para usarse por ejemplo con jsonLike
2238 *
2239 * @param $tableName
2240 * @param $fieldName
2241 * @return string vacia si $tableName.$fieldName no se guarda como json, el property que desplegar/buscar si si
2242 */
2243function fixJsonBuscaEnProperty($tableName, &$fieldName, $data, &$value_txt = ""):string {
2244    if(strlen($data) === 32 && preg_match_all('/[0-9a-f]/miS', $data, $matches, PREG_SET_ORDER, 0)) {
2245        $iactblConJsonFields = [
2246            'nota_bodega' => [4,
2247                'ayudantes_json' => 'id',
2248                'chofer_responsable' => 'figuratransporte_id',
2249                'cliente' => 'cliente_id',
2250                'numero_compra' => 'numero_compra_id',
2251                'pedido_por' => 'id',
2252
2253                'tipo_nota_json' => 'tipo_nota',
2254                'numero_tipo_nota_json' => 'numero_tipo_nota',
2255                'quantity_tipo_nota_json' => 'quantity_tipo_nota',
2256            ],
2257        ];
2258    } else {
2259        $iactblConJsonFields = [
2260            'nota_bodega' => [4,
2261                'ayudantes_json' => 'lbl',
2262                'chofer_responsable' => 'nombre',
2263                'cliente' => 'nombre',
2264                'numero_compra' => 'numero_compra',
2265                'pedido_por' => 'lbl',
2266
2267                'tipo_nota_json' => 'tipo_nota',
2268                'numero_tipo_nota_json' => 'numero_tipo_nota',
2269                'quantity_tipo_nota_json' => 'quantity_tipo_nota',
2270            ],
2271        ];
2272    }
2273
2274    $iactblConJsonFieldsIdx = [
2275        'nota_bodega' => [
2276            'cliente' => ['alt' =>'cliente_actual'],
2277            'numero_compra' => ['alt' =>'numero_compra_actual', 'val_txt' => " = ".strit($data)],
2278        ],
2279    ];
2280    $tableName = trim(strtolower($tableName ?? ''));
2281    $fieldName = trim(strtolower($fieldName ?? ''));
2282
2283    if($iactblConJsonFieldsIdx[$tableName][$fieldName]['alt'] ?? false) {
2284        $value_txt = $iactblConJsonFieldsIdx[$tableName][$fieldName]['val_txt'] ?? "";
2285        $fieldName = $iactblConJsonFieldsIdx[$tableName][$fieldName]['alt'] ?? $fieldName;
2286//        $fi = $iactblConJsonFieldsIdx[$tableName][$fieldName]['fi'] ?? $fi;
2287    }
2288    return $iactblConJsonFields[$tableName][$fieldName] ?? '';
2289}
2290
2291/**
2292 * Regresa un array donde fue_error === 'No'. Limpia y muestra solo los activos
2293 *
2294 * @param string|array|null $json
2295 * @return array
2296 */
2297function getFueErrorNo($json):array {
2298    if(empty($json)) {
2299        return [];
2300    }
2301    if(is_string($json)) {
2302        $json = json_decode($json, true);
2303    }
2304    if(empty($json)) {
2305        return [];
2306    }
2307    return array_values(array_filter(
2308        $json,
2309        function($item) {return strcasecmp('No', $item['fue_error'] ?? 'No')  === 0 ;}
2310    ));
2311}
2312
2313/**
2314 *
2315 *
2316 * @param string|array|null $antes
2317 * @param string|array|null $despues = 'lbl'
2318 * @param string $display
2319 * @return bool true son Iguales, false hay diferencia
2320 */
2321function fueErrorIguales($antes, $despues, $display):bool {
2322    $eran = array_column(getFueErrorNo($antes), $display);
2323    $son = array_column(getFueErrorNo($despues), $display);
2324    $intersection = array_uintersect($eran, $son, 'compareCaseInsensitive');
2325    return empty([
2326        ...array_udiff($eran, $intersection, 'compareCaseInsensitive'),
2327        ...array_udiff($son, $intersection, 'compareCaseInsensitive')
2328    ]);
2329}
2330
2331/**
2332 *
2333 *
2334 * @param string|array|null $antes
2335 * @param string|array|null $despues
2336 * @param string $display = 'lbl'
2337 * @param string $confirma = 'confirma'
2338 * @return bool true son Iguales, false hay diferencia
2339 */
2340function fueErrorConfirmaIguales($antes, $despues, $display, $confirma):bool {
2341    $eran = [];
2342    foreach(getFueErrorNo($antes) as $d)
2343        $eran[] = ($d[$display] ?? '¿?').($d[$confirma] ?? '¿?');
2344    $son = [];
2345    foreach(getFueErrorNo($despues) as $d)
2346        $son[] = ($d[$display] ?? '¿?').($d[$confirma] ?? '¿?');
2347    $intersection = array_uintersect($eran, $son, 'compareCaseInsensitive');
2348    return empty([
2349        ...array_udiff($eran, $intersection, 'compareCaseInsensitive'),
2350        ...array_udiff($son, $intersection, 'compareCaseInsensitive')
2351    ]);
2352}
2353
2354function itemsIguales($antes, $despues):bool   {
2355    if($antes === false || $despues === false)
2356        return true;
2357    $eran = [];
2358    foreach($antes as $a)
2359        $eran[] = bcadd($a['rollos'], '0', 0) . '|' . bcadd($a['quantity'], '0.00', 2) . '|' .
2360            substr($a['producto_bodega_id'], -32);
2361    $son = [];
2362    foreach($despues as $a)
2363        $son[] = bcadd($a['rollos'], '0', 0) . '|' . bcadd($a['quantity'], '0.00', 2) . '|' .
2364            substr($a['producto_bodega_id'], -32);
2365    $intersection = array_uintersect($eran, $son, 'compareCaseInsensitive');
2366    return empty([
2367        ...array_udiff($eran, $intersection, 'compareCaseInsensitive'),
2368        ...array_udiff($son, $intersection, 'compareCaseInsensitive')
2369    ]);
2370}
2371
2372function campoAtipo($campo) {
2373    $modificacionImportante =
2374        ['producto_general_id'=>1, 'items'=>1, 'fecha'=>1, 'entrada_salida'=>1, 'origen_id'=>1,'tipo'=>1,
2375            'total_rolls'=>1, 'total_quantity'=>1, 'paid'=>1, 'contenedor'=>1, 'numero'=>1,
2376        ];
2377    if(array_key_exists($campo, $modificacionImportante))
2378        return 'Modificación Importante';
2379    $destinoMatch = ['ayudantes'=>1, 'ayudantes_json'=>1, 'pedido_por'=>1, 'cliente'=>1, 'numero_compra'=>1];
2380    if(array_key_exists($campo, $destinoMatch))
2381        return 'Destino Match';
2382    $camposCash = ['tipo_nota'=>1, 'tipo_nota_json'=>1, 'pedido_por'=>1, 'numero_tipo_nota'=>1, 'numero_tipo_nota_json'=>1,
2383        'quantity_tipo_nota'=>1, 'quantity_tipo_nota_json'=>1];
2384    if(array_key_exists($campo, $camposCash))
2385        return 'Campos Cash';
2386    $recibido = ['recibido'=>1, 'recibido_el'=>1, 'recibido_por'=>1];
2387    if(array_key_exists($campo, $recibido))
2388        return 'Recibido';
2389    return $campo;
2390}
2391
2392/** @noinspection PhpUnused */
2393function compareCaseInsensitive($a, $b):int {
2394    if(is_array($a) || is_array($b)) {
2395        return $a <=> $b;
2396    }
2397    if($a instanceof \Stringable || $a === null) {
2398        $a = (string)$a;
2399    }
2400    if($b instanceof \Stringable || $b === null) {
2401        $b = (string)$b;
2402    }
2403    if(is_string($a) || is_string($b)) {
2404        return strcasecmp((string)$a, (string)$b);
2405    }
2406    return $a <=> $b;
2407}
2408
2409/**
2410 * @param $fecha_min
2411 * @param $fecha_max
2412 * @return string
2413 */
2414function diasEntreFechas($fecha_min, $fecha_max):string {
2415    if(empty($fecha_min)) {
2416        return '';
2417    }
2418    try {
2419        if(empty($fecha_max)) {
2420            $today = new DateTimeImmutable();
2421            $interval = $today->diff(new DateTimeImmutable($fecha_min), true);
2422        } else {
2423            $minDate = new DateTimeImmutable($fecha_min);
2424            $interval = $minDate->diff(new DateTimeImmutable($fecha_max), true);
2425        }
2426        return " (" .
2427            number_format((int)$interval->format("%a"), 0, '', ',') .
2428            " días)";
2429    } catch(Throwable) {}
2430    return '';
2431}
2432
2433/**
2434 * Regresa la diferencia en frase bonita de las 2 fechas
2435 *
2436 * @param string|int $dateTime1 mysqlDate o mysqlDateTime o timestamp
2437 * @param string|int $dateTime2 mysqlDate o mysqlDateTime o timestamp
2438 * @param int $maxParts
2439 * @return string
2440 */
2441function fechaDiff($dateTime1, $dateTime2, $maxParts = 2):string {
2442    $to = [
2443        'año' => 60*60*24*365,
2444        'mes' => 60*60*24*30,
2445        'día' => 60*60*24,
2446        'hr' => 60*60,
2447        'min' => 60,
2448        'seg' => 1,
2449    ];
2450    $plural = [
2451        'año' => 'años',
2452        'mes' => 'meses',
2453        'día' => 'dias',
2454        'hr' => 'hrs',
2455        'min' => 'min',
2456        'seg' => 'seg',
2457    ];
2458    $last = 'seg';
2459    $format = [];
2460    if($dateTime1 instanceof Stringable) {
2461        $dateTime1 = (string)$dateTime1;
2462    }
2463    if(!is_numeric($dateTime1)) {
2464        $dateTime1 = strtotime($dateTime1);
2465    }
2466    if($dateTime2 instanceof Stringable) {
2467        $dateTime2 = (string)$dateTime2;
2468    }
2469    if(!is_numeric($dateTime2)) {
2470        $dateTime2 = strtotime($dateTime2);
2471    }
2472    $diff = abs( (float)$dateTime1 - (float)$dateTime2);
2473    $breakNow = 0;
2474    foreach($to as $label => $u) {
2475        if($breakNow >= $maxParts || ($breakNow > 0 && $label === $last)) {
2476            break;
2477        }
2478        $period = floor($diff/$u);
2479        if( $period >= 1 ) {
2480            $diff -= $period * $u;
2481            $format[] =  $period . ' ' . ($period === 1.00 ? $label : $plural[$label]);
2482            $breakNow++;
2483        } elseif($breakNow === 1) {
2484            $breakNow++;
2485        }
2486    }
2487    return implode(", ", $format);
2488}
2489
2490/**
2491 * Regresa $data ordenada por los keys de $keyOrder, de existir, en ese orden y luego el resto de keys de $data
2492 *
2493 * @param array<string, mixed> $data
2494 * @param array<int|string, int|string> $keyOrder
2495 * @return array<int|string, mixed>
2496 *
2497 * @example
2498 *      keyOrder(['a' => 'la A', 'b' => 'la b', 'm' => 'la m', 'l'=>'la l', 'k'=>'la k'], ['k', 'm', 'l']);
2499 *          ['k'=>'la k', 'm' => 'la m', 'l'=>'la l','a' => 'la A', 'b' => 'la b']
2500 */
2501function keyOrder(array $data, array $keyOrder):array {
2502    $order = [];
2503    foreach($keyOrder as $key)
2504        if(array_key_exists($key, $data))
2505            $order[$key] = $data[$key];
2506    foreach($data as $key => $v)
2507        if(!array_key_exists($key,$order))
2508            $order[$key] = $v;
2509    return $order;
2510}
2511
2512/**
2513 * Regresa $data ordenada stable por los keys que no están en $keyOrder y luego los keys de $keyOrder, de existir, en ese orden
2514 *
2515 * @param array<int|string, mixed> $data
2516 * @param array<int|string, int|string> $keyOrder
2517 * @return array<int|string, mixed>
2518 * @example
2519 *      keyOrderEnd(['a' => 'la A', 'b' => 'la b', 'm' => 'la m', 'l'=>'la l', 'k'=>'la k'], ['k', 'm', 'l']);
2520 *          ['a' => 'la A', 'b' => 'la b', 'k'=>'la k', 'm' => 'la m', 'l'=>'la l']
2521 */
2522function keyOrderEnd(array $data, array $keyOrder):array {
2523    $order = [];
2524    $by = array_flip($keyOrder);
2525    foreach($data as $key => $v)
2526        if(!array_key_exists($key,$by))
2527            $order[$key] = $v;
2528    foreach($keyOrder as $key)
2529        if(array_key_exists($key, $data))
2530            $order[$key] = $data[$key];
2531    return $order;
2532}
2533
2534function getCurrentValueJson($json):array
2535{
2536    if (empty($json))
2537        return [];
2538    $items_ok = getFueErrorNo($json);
2539
2540    usort($items_ok, function($a, $b) {
2541        return new DateTime($b['alta_db']) <=> new DateTime($a['alta_db']);
2542    });
2543    /*usort($items_ok, function ($a, $b) {
2544        return strtotime($b['alta_db']) - strtotime($a['alta_db']);
2545    });*/
2546    return $items_ok[0]??[];
2547}
2548
2549function displayColumn($campo, $valor):string|bool|int|float
2550{
2551    static $keyValue = [];
2552    if($campo === 'origen_bodega_id')
2553        $campo = 'origen_id';
2554    if($valor === null)
2555        return '';
2556    if(endsWith($campo, '_id')) {
2557        if(array_key_exists($campo, $keyValue))
2558            return $keyValue[$campo][$valor] ?? $valor;
2559        switch ($campo) {
2560            case 'bodega_id':
2561                $keyValue[$campo] = ia_sqlKeyValue("SELECT bodega_id, bodega FROM bodega");
2562                break;
2563            case 'cliente_id':
2564                $keyValue[$campo] = ia_sqlKeyValue("SELECT cliente_id, nombre FROM cliente");
2565                break;
2566            case 'color_id':
2567                $keyValue[$campo] = ia_sqlKeyValue("SELECT color_id, color FROM color");
2568                break;
2569            case 'contra_nota_bodega_id':
2570                return str_replace(';', ', ', $valor);
2571            case 'empresa_id':
2572                $keyValue[$campo] = ia_sqlKeyValue("SELECT empresa_id, empresa FROM empresa");
2573                break;
2574            case 'nota_bodega_id':
2575                return ia_singleread("SELECT label FROM nota_bodega WHERE nota_bodega_id=" . strit($valor));
2576            case 'nota_bodega_items_id':
2577            case 'nota_bodega_item_id':
2578                return ia_singleread("SELECT 
2579                       CONCAT(pg.producto, ' ', c.color, ' ', FORMAT(nbi.rollos, 0), ' rollos, ', FORMAT(nbi.quantity,2), u.unidad )
2580                        FROM nota_bodega_items nbi
2581                            JOIN producto_general pg ON pg.producto_general_id = MID(nbi.producto_bodega_id, 34, 32)
2582                            JOIN unidades u on pg.unidades_id = u.unidades_id
2583                            JOIN color c ON c.color_id = nbi.color_id
2584                        WHERE nota_bodega_item_id=" . strit($valor));
2585            case 'origen_id':
2586                $keyValue[$campo] = ia_sqlKeyValue("SELECT origen_bodega_id, clave FROM origen_bodega");
2587                break;
2588            case 'producto_general_id':
2589                $keyValue[$campo] = ia_sqlKeyValue("SELECT producto_general_id, producto FROM producto_general");
2590                break;
2591            case 'tienda_id':
2592                $keyValue[$campo] = ia_sqlKeyValue("SELECT tienda_id, clave FROM tienda");
2593                break;
2594            case 'unidades_id':
2595                $keyValue[$campo] = ia_sqlKeyValue("SELECT unidades_id, unidad FROM unidades");
2596                break;
2597        }
2598        return $keyValue[$campo][$valor] ?? $valor . '?' . $campo;
2599    }
2600
2601    $camposJsonFueError = [
2602        'ayudantes_json' => ['lbl' => 'lbl'],
2603        'chofer_responsable' => ['lbl' =>'nombre'],
2604        'cliente' => ['lbl' =>'nombre'],
2605        'numero_compra' => ['lbl' =>'numero_compra'],
2606        'pedido_por' => ['lbl' => 'lbl'],
2607
2608        'tipo_nota_json' => ['lbl' =>'tipo_nota'],
2609        'numero_tipo_nota_json' => ['lbl' =>'numero_tipo_nota'],
2610        'quantity_tipo_nota_json' => ['lbl' =>'quantity_tipo_nota'],
2611    ];
2612    if(array_key_exists($campo, $camposJsonFueError)) {
2613        $lbl = $camposJsonFueError[$campo]['lbl'] ?? 'lbl';
2614        //$confirma = $camposJsonFueError[$campo]['confirma'] ?? null;
2615        //if($confirma === null)
2616        return  implode(", ", array_column(getFueErrorNo($valor), $lbl));
2617    }
2618
2619    if(str_contains($campo, 'numero') || str_contains($campo, 'number'))
2620        return $valor;
2621    if(is_numeric($valor)) {
2622        if(str_contains($campo, 'rollos') || str_contains($campo, 'rolls'))
2623            return bcformat((string)$valor, 0);
2624        return bcformat((string)$valor, 2);
2625    }
2626
2627    if('items' === $campo) {
2628        $items = [];
2629        foreach($valor as $i) {
2630            $rollos = bcformat($i['rollos'],0);
2631            $quantity = bcformat($i['quantity'],2);
2632            $producto = displayColumn('producto_general_id', substr($i['producto_bodega_id'],  33, 32) );
2633            $color = displayColumn('color_id', substr($i['producto_bodega_id'],   -32) );
2634            $items[] = "$producto $color $rollos rollos, $quantity";
2635        }
2636        return "<ol><li>" . implode("<li>", $items) . "</li></ol>";
2637    }
2638    return is_array($valor) ? print_r($valor, true) : $valor;
2639}
2640
2641/**
2642 * echo viewJsCode("functionName", $label = 'Show Function'); // pone un div con boton para ver codigo de function
2643 * echo viewJsCode("#id", $label = 'Ver div o javascript o ... con id="id"'); // pone un div con boton para ver codigo del element
2644 * $gDebugging debe ser True!.
2645 *
2646 *
2647 * @param string $show nombre de una funcion de js o un selector #id
2648 * @param string $label Letrero del boton
2649 * @return string
2650 */
2651function viewJsCode($show, $label = '') {
2652    global $gDebugging;
2653    if(!$gDebugging)
2654        return '';
2655    if(empty($label))
2656        $label =  "Show Me: $show";
2657    if($show[0] === '#' || $show[0] === '.') {
2658        $do = "showBySelector(\"$show\", this)";
2659    } else {
2660        $do = "showFunction($show, this)";
2661    }
2662    return <<< JS
2663    <script>
2664        function showFunction(functionToShow, container) {
2665            $(container).parent().html($("<pre>").css({margin:"2em", padding:"1em", border:"1px solid blue"}).
2666                text(functionToShow.toString()));
2667        }
2668        function showBySelector(idToShow, container) {
2669            let pon = $("<pre>").css({margin:"2em", padding:"1em", border:"1px solid blue"}).html( 
2670                $(idToShow)[0].outerHTML.replaceAll('<', "&lt;").replaceAll('\>', "&gt;<br>") );
2671            $(container).parent().html(pon);
2672        }
2673    </script>
2674    <div style="margin:1em"><button  type='button' onclick='$do'>$label</button></div>
2675JS;
2676}
2677
2678/**
2679 * usage: global $gFn; echo <<<HEREDOC Hello, {$gFn(ucfirst($variableNombre))} HEREDOC;
2680 */
2681global $gFn; $gFn = function ($callable) {return $callable;};
2682
2683/**
2684 * " AND color <> {$gStrIt($last_color)}" en vez de " AND color <> " . strit($last_color);
2685 */
2686global $gStrIt; $gStrIt  = function($s) {return strit($s); };
2687global $gPermisoClick;
2688/**
2689 * echo $gPermisoClick('nombrePermiso') o en <<< HTML .. {$permsioClick('puede_autorizar_notas_ultimo_movimiento')}
2690 *
2691 * @param string $permiso
2692 * @return string
2693 */
2694$gPermisoClick = function($permiso, $msg = '', $alwaysVisible = false, $extraParam = '{}') {
2695    if($alwaysVisible) {
2696        $display = 'inline-block';
2697        $cssSuffix = '1';
2698    } else {
2699        $display = 'none';
2700        $cssSuffix = '';
2701    }
2702    $onclick = "onclick='permisador.ponPermisoThis(this, $extraParam);'";
2703    $title = '<div class="ui_tooltip_grid_notas_modificacion_importante_si bold txt_1_1em">Permiso: ' . ucwords(str_replace(['puede_', 'may_', '_'], ' ', $permiso)) . '</div>';
2704
2705    return Permisador::puede_consultar_permisos() !== 'Nada' ?
2706        "<span class='ui-icon ui-icon-key ClickPermiso$cssSuffix pointer tooltip_toolbar' $onclick title='$title' style='display:$display' data-permiso='$permiso' data-extraParam='$extraParam'></span>" .
2707        ($msg === '' ? '' : "<span class='ClickPermiso$cssSuffix pointer' $onclick data-permiso='$permiso' data-extraParam='$extraParam' style='background-color:white;color:blue;display:$display;font-size:14px'>$msg</span>")
2708        : '';
2709};
2710
2711function is_container_number($container_number) {
2712    $container_number = strtoupper( trim($container_number) );
2713    if(!preg_match('/[A-Z]{4}[0-9]{7}/i', $container_number))
2714        return false;
2715    $code = [
2716        0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 8 => 8, 9 => 9,
2717        'A' => 10, 'B' => 12, 'C' => 13, 'D' => 14, 'E' => 15, 'F' => 16, 'G' => 17, 'H' => 18, 'I' => 19, 'J' => 20,
2718        'K' => 21, 'L' => 23, 'M' => 24, 'N' => 25, 'O' => 26, 'P' => 27, 'Q' => 28, 'R' => 29, 'S' => 30, 'T' => 31,
2719        'U' => 32, 'V' => 34, 'W' => 35, 'X' => 36, 'Y' => 37, 'Z' => 38
2720    ];
2721    $sum = 0; $m = 1; $len = strlen($container_number) - 1;
2722    for($i=0; $i < $len; ++$i) {
2723        $sum += $code[$container_number[$i]] * $m;
2724        $m = $m << 1;
2725    }
2726    $checkDigit = $sum - floor($sum/11) * 11;
2727
2728    return $checkDigit == 10 ? $container_number[$len] == '0' : $checkDigit == $container_number[$len];
2729    // http://www.gvct.co.uk/2011/09/how-is-the-check-digit-of-a-container-calculated/
2730/*
2731$ok = 'Ok'; $mal = "<span style='color:red'>X</span>";
2732$cn = 'ymlu8744830'; echo "<li>$cn = " . (is_container_number($cn) ? $ok : $mal);
2733$cn = 'YMLU8744830'; echo "<li>$cn = " . (is_container_number($cn) ? $ok : $mal);
2734$cn = 'TCNU1945814'; echo "<li>$cn = " . (is_container_number($cn) ? $ok : $mal);
2735$cn =  'SEGU5392270'; echo "<li>$cn = " . (is_container_number($cn) ? $ok : $mal);
2736$cn = 'TGBU8864437'; echo "<li>$cn = " . (is_container_number($cn) ? $ok : $mal);
2737
2738$cn = 'GVTU300038'; echo "<li>$cn = " . (is_container_number($cn) ? $ok : $mal);
2739$cn = 'GVTU3000380'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2740
2741$cn = "SEGU5329271"; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2742
2743$cn = 'SEGU5329210'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2744
2745$cn = 'VTU3000389'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2746$cn = 'V U3000389'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2747$cn = '1VTU3000389'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2748$cn = 'G1TU3000389'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2749$cn = 'GV1U3000389'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2750$cn = 'GVT13000389'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2751$cn = 'GVTUa000389'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2752$cn = 'GVTU3a00389'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2753$cn = 'GVTU30a0389'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2754$cn = 'GVTU300a389'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2755$cn = 'GVTU3000a89'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2756$cn = 'GVTU30003a9'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2757$cn = 'GVTU3000380'; echo "<li>$cn erroneo da erroneo: " . (is_container_number($cn) ? $mal : $ok);
2758$cn = 'VTU3000389'; echo "<li>$cn = " . (is_container_number($cn) ? $ok : $mal);
2759$cn = 'GVTU300038'; echo "<li>$cn = " . (is_container_number($cn) ? $ok : $mal);
2760
2761
2762 */
2763}
2764
2765/**
2766 * @param string $fieldName
2767 * @param int|float|bool|string|array|null $value
2768 * @param string $default
2769 * @return string
2770 */
2771function formatMe(string $fieldName, $value, string $default = ''):string {
2772    if($value instanceof stdClass)
2773        $value = json_decode(json_encode($value, JSON_OPTIONS_FOR_MYSQL),true);
2774    if($value === null)
2775        return '';
2776    if($value === true)
2777        return "true";
2778    if($value === false)
2779        return "false";
2780    if(is_array($value))
2781        return formatMeArray($value);
2782
2783    $fieldName = strtolower($fieldName);
2784
2785    if(str_ends_with($fieldName, '_id'))
2786        return formatMeId($fieldName, $value, $default);
2787    $intFlags = [
2788        'atrasado'=>1,
2789        'paid'=>1,
2790        'super_atrasado'=>1,
2791        'pending'=>1,
2792        'modificado'=>1,
2793        'cobrado'=>1,
2794        'delivered_directo'=>1,
2795    ];
2796    if(is_numeric($value) && !str_contains($fieldName, 'num') && !str_contains($fieldName, 'consecutivo')) {
2797        if(str_contains($fieldName, 'lock') || array_key_exists($fieldName, $intFlags))
2798            return (int)$value ? 'Si' : 'No';
2799        if(str_contains($fieldName, 'roll') || str_contains($fieldName, 'verificaciones_') || $fieldName === 'iac_edits')
2800            $decimals = 0;
2801        elseif($fieldName === 'tc' || $fieldName === 'tipo_cambio')
2802            $decimals = 4;
2803        elseif(str_starts_with($fieldName, 'puntos'))
2804            $decimals = 4;
2805        else
2806            $decimals = 2;
2807        return bcformat($value, $decimals);
2808    }
2809
2810
2811    switch($fieldName) {
2812        case 'alta_db':
2813        case 'ultimo_cambio':
2814            return mysqlDateTime2display($value);
2815        case 'fecha':
2816            return mysqlDate2display($value);
2817    }
2818    if(str_contains($value, '-')) {
2819        $len = strlen($value);
2820        if($len === 10)
2821            if(count(explode('-', $value)) === 3)
2822                return mysqlDate2display($value);
2823        if($len === 19)
2824            return mysqlDate2display($value);
2825    }
2826    return (string)$value;
2827}
2828function formatMeArray(array $array, array|null $fieldNameOrder = null):string {
2829    $ret = [" <ol>"]; // espacio inicial importante
2830    if($fieldNameOrder === null)
2831        $fieldNameOrder = ['bodega_id', 'producto_general_id', 'color_id', 'quantity', 'unidades_id', 'rollos'];
2832    foreach($array as $k => $data) {
2833        if(is_array($data)) {
2834            $item = [];
2835            foreach($fieldNameOrder as $fieldName)
2836                if(array_key_exists($fieldName, $data))
2837                    $item[] = formatMe($fieldName, $data[$fieldName]);
2838            $ret[] =  implode(" ", $item);
2839        } else
2840            $ret[] = formatMe($k, $data);
2841    }
2842    return implode(" <li>", $ret) . " </ol>"; // espacio inicial de <li> y </ol> son importantes
2843}
2844
2845function formatMeId(string $fieldName, int|string $id, int|string|null $default = ''):string {
2846    if($default === null)
2847        $default = $id;
2848    $fieldName = strtolower($fieldName);
2849    static $labels;
2850    if(!isset($labels)) {
2851        $method = __METHOD__;
2852        /** @var  $labels
2853         *  Si es un array [fieldname=>[id=>label,...],.. ]
2854         *  String inicia con SELECT y tiene '%id%' sustituye el '%id%' con $id y regresa el label (primera columna)
2855         *  String inicia con SELECT se corre y arma el para la proxima vez fieldname=>[id=>label,...]
2856         *  String no inicia con SELECT hace json_decode(obtenCatalogo(string), true) para llenar el array fieldname=>[id=>label,...]
2857         */
2858        $labels = [
2859            'banco_id' => "SELECT /*$method*/ banco_id, banco FROM banco ORDER BY 2",
2860            'banco_cuenta_id' => "SELECT /*$method*/ banco_cuenta_id, nombre FROM banco_cuenta ORDER BY 2",
2861            'bodega_id' => "bodega_keyValue_all",
2862            'categoria_id' => "categoria",
2863            'cuentat_mov_id' => [],
2864            'cliente_id' => "SELECT /*$method*/ cliente_id, nombre FROM cliente ORDER BY 2",
2865            'color_id' => 'color_keyValue_all',
2866            'cp_autotransporte' =>
2867                "SELECT /*$method*/ cp.autotransporte_id,IF(e.empresa IS NULL,cp.PlacaVM, CONCAT_WS(' ',e.empresa, cp.PlacaVM) ) 
2868                     FROM cp_autotransporte cp LEFT OUTER JOIN empresa e on cp.empresa_id = e.empresa_id 
2869                     ORDER BY 2",
2870
2871            'cp_cliente_id' => "SELECT /*$method*/ cp_cliente_id, razon_social FROM cp_cliente ORDER BY 2",
2872
2873            'cp_figuratransporte_id' => "SELECT /*$method*/ cp.figuratransporte_id,
2874                    IF(e.empresa IS NULL,cp.NombreFigura, CONCAT_WS(' ',e.empresa, cp.NombreFigura) ) 
2875                     FROM cp_figuratransporte cp LEFT OUTER JOIN empresa e on cp.empresa_id = e.empresa_id 
2876                     ORDER BY 2",
2877            'cp_tipofiguratransporte_id' => "SELECT /*$method*/ cp_tipofiguratransporte_id, ClaveFiguraTransporte FROM cp_tipofiguratransporte ORDER BY 2",
2878
2879            'cp_cliente_direccion_id' => "SELECT /*$method*/ CONCAT_WS(' ', calle, numero_exterior, numero_interior, colonia, municipio)  FROM cp_cliente_direccion WHERE cp_cliente_direccion_id='%id%'",
2880
2881            'cuentat_id' => "SELECT /*$method*/ cuentat_id, usuario FROM cuentat ORDER BY 2",
2882            'empresa_id' => "SELECT /*$method*/ empresa_id, empresa FROM empresa ORDER BY 2",
2883            'fabrica_id' => usuarioTipoRony() || Permisador::puede("pedido_china_fabrica", 'Nada') !== 'Nada' ?
2884                "SELECT /*$method*/ fabrica_id, fabrica FROM fabricas ORDER BY 2" : [],
2885            'iac_usr_id' => "SELECT /*$method*/ iac_usr_id, nick FROM iac_usr ORDER BY 2",
2886            'origen_bodega_id' => "SELECT /*$method*/ origen_bodega_id, clave FROM origen_bodega ORDER BY 2",
2887            'producto_bodega_id' =>
2888                "SELECT /*$method*/ producto_bodega_id, 
2889                     CONCAT(b.bodega, ': ', IF(pg.producto IS NULL OR c.color IS NULL, pc.producto, CONCAT_WS(' ', pg.producto, c.color))) 
2890                     FROM producto_bodega pc 
2891                     JOIN bodega b on pc.bodega_id = b.bodega_id    
2892                     LEFT OUTER JOIN producto_general pg on pc.producto_general_id = pg.producto_general_id
2893                     LEFT OUTER JOIN color c on pc.color_id = c.color_id
2894                     ORDER BY 2",
2895            'producto_color_id' =>
2896                    "SELECT /*$method*/ producto_color_id, IF(pg.producto IS NULL OR c.color IS NULL, pc.producto, 
2897                        CONCAT_WS(' ', pg.producto, c.color)) 
2898                     FROM producto_color pc 
2899                     LEFT OUTER JOIN producto_general pg on pc.producto_general_id = pg.producto_general_id
2900                     LEFT OUTER JOIN color c on pc.color_id = c.color_id
2901                     ORDER BY 2",
2902            'producto_general_id' => "producto_general_keyValue_all",
2903            'tienda_id' => "SELECT /*$method*/ tienda_id, clave FROM tienda ORDER BY 2",
2904            'unidades_id' => "SELECT /*$method*/ unidades_id, unidad FROM unidades ORDER BY 2",
2905             'pagare_id' =>
2906                 "SELECT /*$method*/  
2907                    CONCAT(IF(c.nombre IS NULL, '', c.nombre),  ' #', d.numero, 
2908                        ' por $', d.quantity, ' debe $', d.saldo ' ', m.moneda,  
2909                        IF(d.paid = 0, CONCAT(' ', DATE_FORMAT(d.original_date, '%e/%b/%y')), ' PAID'), ' ', c2.categoria
2910                    )
2911                    FROM pagare d
2912                        JOIN moneda m on d.moneda_id = m.moneda_id
2913                        LEFT OUTER JOIN cliente c on d.cliente_id = c.cliente_id 
2914                        LEFT OUTER JOIN categoria c2 on d.categoria_id = c2.categoria_id
2915                    WHERE d.pagare_id='%id%'",
2916
2917            'nota_bodega_id' =>
2918                "SELECT /*$method*/ CONCAT_WS(' ',
2919                            DATE_FORMAT(fecha, '%e/%b/%y'),
2920                            nb.numero, b.bodega, nb.entrada_salida, 
2921                            IF(nb.tipo='Movimiento', 'Venta Tienda', nb.tipo), 
2922                            IF(ob.clave IS NULL OR nb.tipo='Cancelacion' OR nb.tipo='Borrado' OR nb.tipo='Correccion' OR nb.tipo='Container', 
2923                                '', ob.clave)
2924                        )
2925                        FROM nota_bodega nb JOIN bodega b on nb.bodega_id = b.bodega_id 
2926                        LEFT OUTER JOIN origen_bodega ob on nb.origen_id = ob.origen_bodega_id
2927                        WHERE nb.nota_bodega_id='%id%'",
2928        ];
2929        // sinonimos
2930        $labels['origen_id'] = $labels['origen_bodega_id'];
2931        $labels['producto_id'] = $labels['producto_general_id'];
2932        $labels['origen_cuentat_id'] = $labels['cuentat_transferto_id'] = $labels['cuentat_deliveredto_id'] =
2933            $labels['cuentat_id'];
2934
2935    }
2936    if(!array_key_exists($fieldName, $labels))
2937        return $default;
2938    $item = $labels[$fieldName];
2939    if(is_string($item)) {
2940        if(str_contains($item, '%id%'))
2941            return (string)ia_singleread(str_replace("'%id%'" , strit($id), $item), 'N/A', 'SQL ERROR');
2942        if(str_starts_with($item, "SELECT "))
2943            $item = ia_sqlKeyValue($item);
2944        else
2945            $item = json_decode(obtenCatalogo($item), true);
2946    }
2947    if(is_array($item))
2948        return $item[$id] ?? $default;
2949    return $default;
2950}
2951
2952
2953function the_error_handler($errno, $errstr, $errfile, $errline) {
2954    static $errYa;
2955    if (!(error_reporting() & $errno))
2956        return false;
2957    $ya = $errYa[$errfile][$errline] ?? null;
2958    if($ya !== null)
2959        return false;
2960    $errYa[$errfile][$errline] = "$errno" . htmlentities($errstr);
2961    ia_errores_a_dime("$errno" .htmlentities($errstr) , $errfile, 'php', $errline,
2962    $errno.$errfile.$errline);
2963    return false;
2964}
2965function the_exception_handler($exception) {
2966    static $errYa;
2967    $ya = $errYa[$exception->getFile()][$exception->getLine()] ?? null;
2968    if($ya !== null)
2969        return;
2970    $errYa[$exception->getFile()][$exception->getLine()] = htmlentities($exception->getMessage());
2971    ia_errores_a_dime(htmlentities($exception->getMessage(). " FIN DE SCRIPT") , $exception->getFile(), 'php', $exception->getLine(),
2972        $exception->getFile().$exception->getLine());
2973}
2974set_error_handler('the_error_handler');
2975set_exception_handler('the_exception_handler');
2976
2977function recomiendanOlla($ver = 'All') {
2978    $method = __METHOD__;
2979    $puede = Permisador::puede('puede_reporte_ventas');
2980    if(!usuarioTipoRony() && $puede === 'Nada')
2981        return [];
2982
2983    $programar = [];
2984    if(!usuarioTipoRony() && $puede == 'R/O') {
2985        $programarHeader = 'Pedir';
2986    } else {
2987        $programarHeader = "Programa<br>" . ucwords($_SESSION['usuario'] ?? 'N/A');
2988        if($ver !== 'Olla') {
2989            $sql = "SELECT /*$method*/ CONCAT(pg.producto, '_', c.color) as id,  pg.producto, c.color,
2990             IF(digo = 'No Pedir', 'No Pedir', 
2991                 IF(digo = 'Complemento', CONCAT('Complemento ', IF(pri.quantity IS NULL OR pri.quantity=0, ' ', pri.quantity )  )   
2992                     ,pri.quantity) ) as quantity
2993        FROM pedido_recomienda_items pri
2994            JOIN pedido_recomienda pr on pri.pedido_recomienda_id = pr.pedido_recomienda_id
2995            JOIN iac_usr iu on pr.iac_usr_id = iu.iac_usr_id
2996            JOIN producto_general pg on pri.producto_general_id = pg.producto_general_id
2997            JOIN color c on pri.color_id = c.color_id
2998        WHERE pr.activo = 'Si' AND pr.tipo = 'Programacion' AND pri.digo <> '' AND pr.iac_usr_id=" . strit($_SESSION['usuario_id']) .
2999            " ORDER BY 2, 3";
3000            $programar = ia_sqlArray($sql, 'id');
3001        }
3002    }
3003    if($ver === 'Programacion' || $ver === 'Programa')
3004        $olla = [];
3005    else {
3006        $sql = "SELECT /*$method*/ pg.producto, c.color, iu.nick, 
3007             IF(digo = 'No Pedir', 'No Pedir', 
3008                 IF(digo = 'Complemento', CONCAT('Complemento ', IF(pri.quantity IS NULL OR pri.quantity=0, ' ', pri.quantity )  )   
3009                     ,pri.quantity) ) as quantity
3010        FROM pedido_recomienda_items pri
3011            JOIN pedido_recomienda pr on pri.pedido_recomienda_id = pr.pedido_recomienda_id
3012            JOIN iac_usr iu on pr.iac_usr_id = iu.iac_usr_id
3013            JOIN producto_general pg on pri.producto_general_id = pg.producto_general_id
3014            JOIN color c on pri.color_id = c.color_id
3015        WHERE pr.activo = 'Si' AND pr.tipo = 'Olla' AND pri.digo <> ''
3016        ORDER BY 1, 2, 3";
3017        $olla = ia_sqlSelectMultiKey($sql, 3);
3018    }
3019    $users = [];
3020    foreach($olla as $colores)
3021        foreach($colores as $usuarios)
3022            foreach($usuarios as $nick => $_)
3023                $users[$nick] = '';
3024
3025    $rec = [];
3026    foreach($olla as $producto => $colores) {
3027        foreach($colores as $color => $usuarios) {
3028            $key = $producto.'_'.$color;
3029            $rec[$key] =[
3030                ...['producto' => $producto, 'color' => $color, $programarHeader => $programar[$key]['quantity'] ?? ''],
3031                ...$users
3032            ];
3033            $in = &$rec[$key];
3034            foreach($usuarios as $nick => $r) {
3035                $in[$nick] = $r['quantity'];
3036            }
3037        }
3038    }
3039
3040    foreach(array_diff_key($programar, $rec) as $key => $c)
3041        $rec[$key] = ['producto' => $c['producto'], 'color' => $c['color'], $programarHeader => $c['quantity']];
3042    return $rec;
3043}
3044function recomiendanOllaTable($ver = 'All') {
3045    $date = mysqlDateTime2display(Date('Y-m-d H:i:s'));
3046    $data = recomiendanOlla($ver);
3047    uasort($data, function($a, $b){
3048        $da = strtoupper($a['producto']) <=> strtoupper($b['producto']);
3049        return $da ? $da : strtoupper($a['color']) <=> strtoupper($b['color']);
3050    });
3051    return
3052        '<script>
3053    function tablaDaVer() {
3054        let numCols =  $("#tablaDa tr").first().children().length;
3055        switch($("input[name=\'tablaDaColsVer\']:checked").val()) {
3056        case "Olla":
3057            $("td:nth-child(3)", "#tablaDa").hide();
3058            $("th:nth-child(3)", "#tablaDa").hide();
3059            for(let i = 4; i <= numCols; ++i) {
3060                $(`td:nth-child(${i})`, "#tablaDa").show();
3061                $(`th:nth-child(${i})`, "#tablaDa").show();
3062            }
3063            break;
3064        case "Programa":
3065        case "Programacion":
3066            $("td:nth-child(3)", "#tablaDa").show();
3067            $("th:nth-child(3)", "#tablaDa").show();
3068            for(let i = 4; i <= numCols; ++i) {
3069                $(`td:nth-child(${i})`, "#tablaDa").hide();
3070                $(`th:nth-child(${i})`, "#tablaDa").hide();
3071            }
3072            break;
3073        default:
3074            for(let i = 3; i <= numCols; ++i) {
3075                $(`td:nth-child(${i})`, "#tablaDa").show();
3076                $(`th:nth-child(${i})`, "#tablaDa").show();
3077            }
3078        }
3079    }
3080    function tablaDaExcel() { 
3081        let ver = $("input[name=\'tablaDaColsVer\']:checked").val();
3082        window.open("../bodega/olla2excel.php?ver=" + ver, "_blank");
3083       setTimeout(function(){ $("#tablaDaExcel").off("click", tablaDaExcel).one("click", tablaDaExcel);}, 700);
3084    }
3085    jQuery(function(){
3086        $("#recomiendaExport").append(exporter.toolBar("#tablaDa",{excel_table:false, fileName:"programar.csv"} ) );
3087        $("#tablaDaExcel").off("click", tablaDaExcel).one("click", tablaDaExcel);
3088    });
3089        </script><style>#tablaDa CAPTION{text-align:left}</style><div style="text-align: left;width:fit-content;">' .
3090        iaTableIt::getTableIt(
3091            $data,
3092            "<div id='recomiendaExport' style='white-space: nowrap;padding:0.5em 0.3em'>Recomendaciones para Programar <i style='font-weight: 100'>" . ucwords($_SESSION['usuario'] ?? 'N/A') . " $date</i><p style='padding-top:0.5em'>" .
3093                " <input onchange='tablaDaVer()' type='radio' name='tablaDaColsVer' id='tablaDaAll' value='All'><label for='tablaDaAll'> All</label> " .
3094                " <input onchange='tablaDaVer()' type='radio' name='tablaDaColsVer' id='tablaDaOlla' value='Olla'><label for='tablaDaOlla'> Olla</label> " .
3095                " <input onchange='tablaDaVer()' type='radio' name='tablaDaColsVer' id='tablaDaPrograma' value='Programa' checked><label for='tablaDaPrograma'> Programa</label> " .
3096                " <span id='tablaDaExcel' class='pointer' style='margin-left:1em;color:darkgreen;font-size:1.2em;font-weight: bold' title='Exportar a Excel'>🅇</span>" .
3097                "</div>",
3098            "tablaDa"
3099        ) . '</div>';
3100}
3101
3102/**
3103 * @param string $producto_general_id
3104 * @param string $color_id
3105 * @param string|int|float|null $quantity
3106 * @param string $digo '' borrado ya no esta capturado
3107 * @param string $remarks
3108 * @param string $tipo Olla o Programacion
3109 * @param bool $execute
3110 * @return bool|string
3111 * @throws Exception
3112 */
3113function recomiendaSave(
3114    string $producto_general_id, string $color_id,
3115    string|int|float|null  $quantity,
3116    #[ExpectedValues(['Pedir', 'Falta', 'Complemento', 'No', 'No Pedir', 'Importante', ''])]
3117    string $digo,
3118    string $remarks = '',
3119    #[ExpectedValues(['Olla', 'Programacion'])]
3120    string $tipo = 'Olla',
3121    bool $execute = true
3122):bool|string {
3123    if(strcasecmp($tipo, 'Olla') && strcasecmp($tipo, 'Programacion'))
3124        return false;
3125    $function = __FUNCTION__;
3126    $builder = new \Iac\inc\sql\IacSqlBuilder();
3127    $digo = ucwords(strtolower($digo ?? ''));
3128    $pedido_recomienda_id = getRecomiendaPedido(strcasecmp($tipo, 'Olla') === 0);
3129
3130    if(($digo === 'Pedir' && empty($quantity)) || $digo === '') {
3131        $where = $builder->where([
3132            'producto_general_id' => $producto_general_id,
3133            'color_id' => $color_id,
3134            'pedido_recomienda_id' => $pedido_recomienda_id,
3135        ]);
3136        $sqlDelete[] = "DELETE /*$function*/ FROM pedido_recomienda_items WHERE $where";
3137        if (!$execute)
3138            return $sqlDelete[0];
3139        $sqlDelete[] = "DELETE /*$function*/ FROM pedir_producto WHERE " .
3140            $builder->where(['iac_usr_id' => $_SESSION['usuario_id'], 'producto_general_id' => $producto_general_id, 'color_id' => $color_id]);
3141        return !ia_transaction($sqlDelete);
3142    }
3143    # revisa datos
3144    if(!empty($quantity) && (!is_numeric($quantity) || ((float)$quantity < 0) ))
3145        return false;
3146
3147    if(!array_key_exists($digo, ['Pedir'=>1, 'Falta'=>1, 'Complemento'=>1, 'Importante'=>1, 'No'=>1, 'No Pedir'=>1, ''=>1, 'Ya Pedido' => 1] ) )
3148        return false;
3149
3150    if($digo === 'No Pedir')
3151        $quantity = 0;
3152    elseif($quantity == '0' && $digo !== 'Complemento' && $digo !== 'Importante')
3153        $digo = 'No Pedir';
3154    if($quantity === '') {
3155        $quantity = null;
3156        if($digo !== 'Complemento' && $digo !== 'No Pedir' && $digo !== 'Importante' && $digo !== 'Ya Pedido')
3157            $digo = '';
3158    }
3159    $era = json_encode([ ['digo' => $digo, 'quantity' => $quantity] ], JSON_OPTIONS_FOR_MYSQL);
3160    $pedido_recomienda_id = getRecomiendaPedido(strcasecmp($tipo, 'Olla') === 0);
3161    $data = [
3162        'pedido_recomienda_id' => strit($pedido_recomienda_id),
3163        'producto_general_id' => strit($producto_general_id),
3164        'color_id' => strit($color_id),
3165        'quantity' => $quantity === null ? 'NULL' : strit($quantity),
3166        'digo' => strit($digo),
3167        'era' => strit($era),
3168    ];
3169    $insert = "INSERT /*$function()*/ INTO pedido_recomienda_items(" . implode(',', array_keys($data)) . ") " .
3170        "VALUES (" . implode(',', $data) . ") ON DUPLICATE KEY UPDATE ";
3171    foreach($data as $fieldName => $value)
3172        if($fieldName === 'era')
3173            $insert .= " era = $value";
3174        else
3175            $insert .= " $fieldName = $value,";
3176    $insert .= "ultimo_cambio = NOW()";
3177
3178    if (!$execute)
3179        return $insert;
3180    $sql = [$insert];
3181
3182    if($digo === '')
3183        $sql[] = "DELETE /*$function*/ FROM pedir_producto WHERE " .
3184            $builder->where(['iac_usr_id' => $_SESSION['usuario_id'], 'producto_general_id' => $producto_general_id, 'color_id' => $color_id]);
3185    else {
3186        $pedir_producto = [
3187            'producto_general_id' => $producto_general_id,
3188            'color_id' => $color_id,
3189            'iac_usr_id' => $_SESSION['usuario_id'],
3190            'quantity' => $quantity ?? '',
3191            'comentario' => '',
3192            'pedido_recomienda_items_id' => '',
3193            'digo' => $digo,
3194        ];
3195        $sql[] = $builder->insert('pedir_producto', $pedir_producto, true, '', ['comentario']);
3196    }
3197
3198    return !ia_transaction($sql);
3199}
3200
3201function topVentasBodega($top = 10) {
3202    $header = [];
3203    $ret = [];
3204    $lastYear = (int)Date('Y') - 1;
3205    $fechas = [
3206        'Todo' => ['2019-01-01', Date('Y-m-d')],
3207        'Este Mes' => [Date('Y-m-01'), Date('Y-m-d')],
3208        '1 Mes' => [Date('Y-m-d', strtotime('1 month ago')), Date('Y-m-d')],
3209        '2 Meses' => [Date('Y-m-d', strtotime('2 month ago')), Date('Y-m-d')],
3210        '3 Meses' => [Date('Y-m-d', strtotime('3 month ago')), Date('Y-m-d')],
3211        '4 Meses' => [Date('Y-m-d', strtotime('4 month ago')), Date('Y-m-d')],
3212        '6 Meses' => [Date('Y-m-d', strtotime('6 month ago')), Date('Y-m-d')],
3213        '12 Meses' => [Date('Y-m-d', strtotime('1 year ago')), Date('Y-m-d')],
3214
3215        'Este Año' => [Date('Y-01-01'), Date('Y-m-d')],
3216        'El Pasado' => [Date('$lastYear-01-01'), Date('$lastYear-m-d')],
3217        'El Año Anterior' => [Date('$lastYear-01-01'), Date('$lastYear-12-31')],
3218    ];
3219    $sql = "SELECT CONCAT(pg.producto,' ', c.color) as producto,
3220                SUM(bed.salida_venta_quantity - bed.entrada_devolucion_quantity) as quantity,
3221                u.unidad,
3222                SUM(bed.salida_venta_rollos - bed.entrada_devolucion_rollos) as rollos 
3223                FROM bodega_existencia_diaria bed 
3224                    JOIN producto_general pg ON bed.producto_general_id=pg.producto_general_id
3225                    JOIN unidades u on pg.unidades_id = u.unidades_id
3226                    JOIN color c ON bed.color_id=c.color_id
3227                WHERE fecha >= ? AND fecha <= ?
3228                GROUP BY producto, u.unidad ORDER BY 2 DESC LIMIT $top";
3229
3230    global $gSqlClass;
3231    $query = $gSqlClass->mysqli->prepare($sql);
3232
3233    foreach($fechas as $label => $f) {
3234        $rows = [];
3235        $n = 0;
3236        $header = "<th class='cen' colspan='4'>$label<div class='vdet'>" . mysqlDate2display($f[0]) . ' - ' . mysqlDate2display($f[1]) . "</div>";
3237        $query->bind_param('ss', $f[0], $f[1]);
3238        foreach(ia_sqlArrayIndx($query) as $p) {
3239            $n++;
3240            $rows[] = "<td class='der'>$n<td>$p[producto]<td class='der'>" .
3241                bcformat($p['quantity'], 2) . "<td class='cen'>$p[unidad]";
3242        }
3243        if(!empty($rows))
3244            $ret[] = "<table class='laTabla'><thead><tr>$header</thead><tbody><tr>" .
3245                implode("<tr>", $rows) . "</tbody></table>";
3246
3247    }
3248
3249    return "<div style='display:flex;flex-wrap: wrap'>" . implode("", $ret) . "</div>";
3250}
3251
3252/**
3253 * @param $url
3254 * @param $iac_usr_id
3255 * @return array ['nombre'=>'valor',...] para los defaults de iac_usr_id en la hoja $url
3256 */
3257function usuario_defaults_get(string $url='', string|int|float $iac_usr_id=''):array {
3258    global $gWebDir;
3259    if(empty($iac_usr_id))
3260        $iac_usr_id = $_SESSION['usuario_id'];
3261    if(empty($url))
3262        $url = str_replace("$_SERVER[CONTEXT_DOCUMENT_ROOT]/$gWebDir", "", $_SERVER['SCRIPT_FILENAME']);
3263    $method = __FUNCTION__;
3264    $defautls = ia_sqlKeyValue(
3265        "SELECT /*$method*/ nombre, valor 
3266            FROM usuario_defaults 
3267            WHERE url=" . strit($url) . " AND iac_usr_id=" . strit($iac_usr_id));
3268    if(empty($defautls) )
3269        return [];
3270    foreach($defautls as &$d)
3271        $d = json_decode($d, true);
3272    return $defautls;
3273}
3274
3275function usuario_defaults_set(array $keyValue, string $url='', string|int|float $iac_usr_id='') {
3276    global $gWebDir;
3277    if(empty($iac_usr_id))
3278        $iac_usr_id = $_SESSION['usuario_id'];
3279    if(empty($url))
3280        $url = str_replace("$_SERVER[CONTEXT_DOCUMENT_ROOT]/$gWebDir", "", $_SERVER['SCRIPT_FILENAME']);
3281    $builder = new \Iac\inc\sql\IacSqlBuilder();
3282    foreach($keyValue as $nombre => $valor) {
3283        ia_query($builder->insert(
3284            'usuario_defaults',
3285            ['url' => $url, 'iac_usr_id' => $iac_usr_id, 'nombre' => $nombre,
3286                'valor' => json_encode($valor, JSON_OPTIONS_FOR_MYSQL)],
3287            true)
3288        );
3289    }
3290}
3291
3292/**
3293 * Tablita con los archivos y su last modified time relacionados. Hmm Deducidos, agregar $extraFiles as needed
3294 *
3295 * @param array $extraFiles
3296 * @return string
3297 */
3298function usedFilesTimes(array $extraFiles = []):string {
3299    global $gDefaultClasses, $gIApath, $gIaHeader, $gVersionesExtraFiles;
3300    $docRoot = substr( $gIApath['DOCUMENT_ROOT'], 0 ,-1);
3301    $baseName = basename($_SERVER['SCRIPT_FILENAME']);
3302    $currentPath = str_replace($baseName, '', $_SERVER['SCRIPT_FILENAME']);
3303    $filePath = $gIApath['FilePath'];
3304    $used = [];
3305
3306    $dime = getMTime($baseName);
3307    $used[$dime['file']] = $dime['mtime'];
3308    $dime = getMTime($baseName . '_acciones');
3309    if(trim($dime['mtime']) !== 'N/A')
3310        $used[$dime['file']] = $dime['mtime'];
3311    $forceFiles = [
3312      "backoffice/view/component/commons/dialog_consulta_existenciaColapsable.php",
3313      "backoffice/view/component/commons/dialogoExistencias.js",
3314      "backoffice/view/component/commons/dialogoReporteVentas.js",
3315      ""
3316    ];
3317    foreach($forceFiles as $file) {
3318        $file = $filePath . $file;
3319        if(file_exists($file))
3320            $used[$file] = Date('Y-m-d H:i', filemtime($file));
3321        else
3322            $used[$file] = " N/A";
3323    }
3324    foreach($gVersionesExtraFiles as $file) {
3325        switch($file[0]) {
3326            case '.': $file = $currentPath . $file; break;
3327            case '/': break;
3328            default: $file = $filePath . $file;
3329        }
3330        if(file_exists($file))
3331            $used[$file] = Date('Y-m-d H:i', filemtime($file));
3332        else
3333            $used[$file] = " N/A";
3334    }
3335
3336    $code = file_get_contents($_SERVER['SCRIPT_FILENAME']);
3337    if(empty($code))
3338        $code = '';
3339    $code .= $gIaHeader->html_head_get();
3340
3341    preg_match_all('/(include|require)(_once){0,1}\s*\(\s*["\'](.*)["\']\)/mUS',
3342        $code, $matches, PREG_SET_ORDER, 0);
3343    if(!empty($matches))
3344        foreach($matches as $m) {
3345            $dime = getMTime(basename($m[3]));
3346            if(trim($dime['mtime']) !== 'N/A') {
3347                $used[$dime['file']] = $dime['mtime'];
3348                continue;
3349            }
3350            $file = $currentPath . $m[3];
3351            if(file_exists($file))
3352                $used[$file] = Date('Y-m-d H:i' ,filemtime($file));
3353            else
3354                $used[$file] = ' N/A';
3355        }
3356    unset($matches);
3357
3358    preg_match_all('/<script\s*src\s*=\s*[\'"](.*)[\'"]/mUS',
3359        $code, $matches, PREG_SET_ORDER, 0);
3360    if(!empty($matches))
3361        foreach($matches as $m) {
3362            $file =$m[1];
3363            $iPos = strpos($file, '?');
3364            if($iPos)
3365                $file = substr($file, 0, $iPos);
3366            if($file[0] === '.')
3367                $file = $currentPath . $file;
3368            elseif($file[0] === '/')
3369                $file = $docRoot . $file;
3370            if(file_exists($file))
3371                $used[$file] = Date('Y-m-d H:i' ,filemtime($file));
3372            else
3373                $used[$file] = ' N/A' . "de SCRIPT" ;
3374        }
3375    unset($matches);
3376
3377    preg_match_all('/<link\s*href\s*=\s*[\'"](.*)[\'"]/mUS',
3378        $code, $matches, PREG_SET_ORDER, 0);
3379    if(!empty($matches))
3380        foreach($matches as $m) {
3381            $file =$m[1];
3382            $iPos = strpos($file, '?');
3383            if($iPos)
3384                $file = substr($file, 0, $iPos);
3385            if($file[0] === '.')
3386                $file = $currentPath . $file;
3387            elseif($file[0] === '/')
3388                $file = $docRoot . $file;
3389            if(file_exists($file))
3390                $used[$file] = Date('Y-m-d H:i' ,filemtime($file));
3391            else
3392                $used[$file] = ' N/A' . "de LINK";
3393        }
3394    unset($matches);
3395
3396    preg_match_all('/url\s*:\s*[\'"](.*)[\'"]/mUS',
3397        $code, $matches, PREG_SET_ORDER, 0);
3398    if(!empty($matches))
3399        foreach($matches as $m) {
3400            $file =$m[1];
3401            $iPos = strpos($file, '?');
3402            if($iPos)
3403                $file = substr($file, 0, $iPos);
3404            if($file[0] === '.')
3405                $file = $currentPath . $file;
3406            elseif($file[0] === '/')
3407                $file = $docRoot . $file;
3408            if(file_exists($file))
3409                $used[$file] = Date('Y-m-d H:i' ,filemtime($file));
3410            else
3411                $used[$file] = ' N/A' . "de LINK";
3412        }
3413    unset($matches);
3414
3415
3416    $inc = ['config.php', 'ia_utilerias.php', 'helpers.php', 'vitex.php'];
3417    foreach($inc as $file) {
3418        $dime = getMTime($file);
3419        $used[$dime['file']] = $dime['mtime'];
3420    }
3421    foreach($extraFiles as $file) {
3422        $dime = getMTime($file);
3423        $used[$dime['file']] = $dime['mtime'];
3424    }
3425
3426    foreach(get_declared_classes() as $class) {
3427        switch($class) {
3428            case 'ProcessLock': $class = 'vitex_process_lock'; break;
3429            case 'sqlMysqli': $class = 'sqlConverter'; break;
3430            case 'StringCompareJaroWinkler': $class = 'vitex_experimental'; break;
3431            case 'XLSXWriter': $class = 'xlsxwriter.class'; break;
3432            case 'XLSXWriter_BuffererWriter': $class = 'xlsxwriter.class'; break;
3433            case 'BancoCuentaMov': $class = 'tipos'; break;
3434            case 'Iac\inc\sql\IacSqlException': continue 2;
3435        }
3436
3437        if( !array_key_exists($class, $gDefaultClasses) ) {
3438            $classFileName = basename($class) . ".php";
3439            $dime = getMTime($classFileName);
3440            $used[$dime['file']] = $dime['mtime'];
3441        }
3442    }
3443
3444    arsort($used);
3445
3446    $count = count($used);
3447    $first = reset($used);
3448    $caption = "Versiones: " . basename($_SERVER['SCRIPT_FILENAME']) . " uses $count files. Último Cambio el $first";
3449    $ret[] = "<table id='versionesFilesMTime' class='laTabla'><caption>$caption</caption><thead><tr><th>File</th><th>Last Modified</th></tr></thead><tbody>";
3450    foreach($used as $class => $t)
3451        $ret[] = "<tr><td>$class<td>$t";
3452    $ret[] = "</tbody></table>";
3453    return "<details class='default' style='margin:1em;'><summary>$caption</summary><div>" . implode("", $ret) . "</div></details>";
3454}
3455
3456/**
3457 * Ayudante de usedFilesTimes
3458 *
3459 * @param string $file
3460 * @return array|string[]
3461 */
3462function getMTime($file):array {
3463    global $gIApath;
3464    static $path;
3465    if(empty($path)) {
3466        $path = explode(";", ini_get('include_path'));
3467        $path[] = "$gIApath[FilePath]backoffice";
3468        $path[] = "$gIApath[FilePath]backoffice/ajax";
3469        $path[] = "$gIApath[FilePath]bodega";
3470        $path[] = "$gIApath[FilePath]cobranza";
3471        $path[] = "$gIApath[FilePath]pedido";
3472        $path[] = "$gIApath[FilePath]css2";
3473        $path[] = "$gIApath[FilePath]js2";
3474    }
3475    foreach($path as $p)
3476        if(file_exists("$p/$file"))
3477            return ['file' => "$p/$file", 'mtime' => Date('Y-m-d H:i', filemtime("$p/$file") )];
3478    $p = "$gIApath[FilePath]inc";
3479    foreach(['ErrorReporter', 'sql', 'Excel', 'iaJqGrid'] as $subDir)
3480        if(file_exists("$p/$subDir/$file"))
3481            return ['file' => "$p/$subDir/$file", 'mtime' => Date('Y-m-d H:i', filemtime("$p/$subDir/$file") )];
3482    return ['file' => "$file", 'mtime' => ' N/A'];
3483}
3484
3485function getLastNModifiedFiles($n = 25) {
3486
3487    $file = fopen("C:\\wamp\\www\\vitex\\uploads\\fragmentLastModified.html","r");
3488    while ($line = fgets($file, 4 * 4096)) {
3489        echo mb_convert_encoding($line, "UTF-8", "UTF-16");
3490    }
3491    return true;
3492/*
3493    $cmd = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe \"dir c:\\wamp\\www\\vitex\\ -r | where { \$_.fullname -notmatch 'txt|uploads|testie|idea|vscode|fontawesome-pro-6.4.0-web|vendor|json'} | Sort-Object LastWriteTime -Descending | Select-Object -first $n | select lastwritetime,hack,fullname\"";
3494    $output = shell_exec($cmd);
3495
3496    preg_match('/^(\d.*)\s{3,}.*$/mS', $output, $matches);
3497    return "<details class='default'><summary>Last $matches[1]</summary><div>Last $n modified<pre class='code'>$output</pre></div></details>";*/
3498}
3499
3500/**
3501 * @return void
3502 * @noinspection PhpUnused
3503 */
3504function file_debug_reporte():void {
3505    global $gSqlClass, $gIApath, $gDime;
3506    $date = Date('d/M/y H:i');
3507    $fileName = basename($_SERVER['REQUEST_URI']);
3508    if(str_contains($fileName, '?'))
3509        $fileName = strstr(basename($fileName), '?', true);
3510    if(str_contains($fileName, '&'))
3511        $fileName = strstr(basename($fileName), '&', true);
3512    if(str_contains($fileName, '#'))
3513        $fileName = strstr(basename($fileName), '#', true);
3514
3515    $css = iaTimer::cssClases() . iaTableIt::getCssClases();
3516    $fontFamily = 'font-family: "Source Code Pro", "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
3517    $sqlTrace = $gSqlClass->trace_get();
3518    $explain = [];
3519    foreach($sqlTrace as $id => $sql) {
3520        $s = ltrim(strtolower($sql));
3521        if(
3522            !str_starts_with($s, 'show') && !str_starts_with($s, 'create') &&
3523            !str_starts_with($s, 'alter') && !str_starts_with($s, 'drop') &&
3524            !str_starts_with($s, 'explain') &&
3525            !str_starts_with($s, 'set') && !str_starts_with($s, 'call') &&
3526            !str_starts_with($s, 'commit') && !str_starts_with($s, 'rollback')
3527        )
3528            $explain[$id] = "<li><pre>$sql</pre><pre>" . iaTableIt::getTableIt(ia_sqlArrayIndx("EXPLAIN $sql")) . "</pre>";
3529        else
3530            $explain[$id] = "<li><pre>$sql</pre>";
3531    }
3532    $iah = param('iah', param('h'));
3533    if(!empty($iah))
3534        $fileName .= "_iah_$iah";
3535    file_put_contents("$gIApath[FilePath]backoffice/txt/quePex_$fileName.html",
3536        "<!DOCTYPE html><html lang='es-MX'><head><title>q!Pex: $fileName</title><style>
3537            BODY{margin:1em;padding:1em;$fontFamily
3538            PRE {margin:1em;padding:1em;border:1px blue solid;$fontFamily;width:fit-content}
3539            $css FIELDSET {width:fit-content}        
3540            TABLE {white-space: normal}  
3541        </style></head><body><h1>q!Pex: $fileName</h1><br>$date $_SESSION[usuario]<pre><fieldset>" .
3542        "<fieldset><legend>Request</legend>" .
3543            ToCode::variable('Request',  $_REQUEST) . "\r\n" .
3544        "</fieldset>" .
3545        "<fieldset><legend>\$gDime</legend>" .
3546            ToCode::variable('gDime',  empty($gDime) ? [] : $gDime) . "\r\n" .
3547        "</fieldset>" .
3548        "<fieldset><legend>Detected Errors</legend>" .
3549            ToCode::variable('PHP Last Error',  error_get_last()) . "\r\n" .
3550            ToCode::variable('PHP Last JsonError',  json_last_error_msg()) . "\r\n" .
3551            ToCode::variable('PHP Last RegExpError',  preg_last_error_msg()) . "\r\n" .
3552            ToCode::variable('Sql_Errors', $gSqlClass->errorLog_get()) . "\r\n" .
3553        "</fieldset>" .
3554          "<fieldset><legend>Queries</legend>\r\n\r\n<ol>" . implode("\r\n\r\n", $explain)  . "\r\n\r\n</ol>\r\n\r\n</fieldset>" . "\r\n" .
3555        "</fieldset>" .
3556        "<fieldset><legend>Timing</legend>" .
3557        iaTimer::table(null, true, true, true) .
3558        "</fieldset>" .
3559        "</pre>" .
3560        "</body></html>"
3561    );
3562}
3563
3564function keys2Tiny(array $data, $rowKeysToCompress = []) {
3565    if(empty($data))
3566        return ['data'=>[], 'keyMap' => []];
3567
3568    $colN = 0;
3569    $keyMap = [];
3570    foreach(array_keys(reset($data)) as $key)
3571        $keyMap[$key] = num2Key($colN++);
3572    $rowKeys = array_flip($rowKeysToCompress);
3573    $reKeyed = [];
3574    foreach($data as $k => $d) {
3575        $row = [];
3576        foreach($d as $key => $value)
3577            if(array_key_exists($key, $rowKeys)) {
3578                if(!array_key_exists($value, $keyMap)) {
3579                    $keyMap[$value] = num2Key(++$colN);
3580                }
3581                $row[$keyMap[$key]] = $keyMap[$value];
3582            } else
3583                $row[$keyMap[$key]] = $value;
3584        $reKeyed[$k] = $row;
3585    }
3586    return [
3587         'data' => $reKeyed,
3588        'keyMap' => $keyMap, 'rowKeyMap' => $rowKeys];
3589}
3590
3591/**
3592 * De número (0=A) a columna de excel
3593 *
3594 * @param int $colNum
3595 * @return string
3596 */
3597function num2Key(int $colNum):string {
3598    if($colNum < 0)
3599        return 'A';
3600    if($colNum < 26) {
3601        return chr($colNum + 65);
3602    }
3603    if($colNum < 702) {
3604        return chr(64 + (int)($colNum / 26)) . chr(65 + $colNum % 26);
3605    }
3606    return chr(64 + (int)(($colNum - 26) / 676)) . chr(65 + (int)((($colNum - 26) % 676) / 26)) . chr(65 + $colNum % 26);
3607}
3608
3609function getDeviceInfo():array {
3610    $device['ua'] = strtolower($_SERVER["HTTP_USER_AGENT"] ?? '');
3611    $device['isMobile'] = str_contains($device['ua'], 'mobile') || str_contains($device['ua'], 'phone');
3612    $device['isTablet'] = str_contains($device['ua'], "ipad") || str_contains($device['ua'], "tablet");
3613    $device['isIpad'] = str_contains($device['ua'], "ipad");
3614    return $device;
3615}
3616
3617/**
3618 * Regresa html de un select box con las posiciones de columnas a usar de default, al seleccionar va a la hoja
3619 *
3620 * @param string $url pagina a que dirigirse ie
3621 * @param string $col_def_key ie posiciones de la hoja
3622 * @return string
3623 */
3624function jqGridColsSorter_SelectLinkTo(string $url, string $col_def_key):string {
3625    static $colTemplates;
3626    if(!is_array($colTemplates)) {
3627        $function = __FUNCTION__;
3628        $usuario = strit($_SESSION['usuario_id']);
3629        $colTemplates =  ia_sqlSelectMultiKey(
3630            "SELECT /*$function*/ col_def_key, jqGridColsSorter_id, label, para_todos
3631        FROM jqGridColsSorter
3632        WHERE iac_usr_id = $usuario OR para_todos = 'Si' OR jqGridColsSorter_id <= 0
3633        ORDER BY myDefault, label",2);
3634    }
3635    if(!array_key_exists($col_def_key, $colTemplates))
3636        return '';
3637    if(str_contains($url, '?'))
3638        $url .= '&';
3639    else
3640        $url .= '?';
3641    $options = "<option></option>";
3642    foreach($colTemplates[$col_def_key] as $id => $d) {
3643        if($id <= 0)
3644            $class = 'colSortDefault';
3645        elseif($d['para_todos'] === 'No')
3646            $class = 'colSortUsuario';
3647        else
3648            $class = 'colSortParaTodos';
3649        $options .= "<option value='{$url}jqGridColsSorter_id=$id' class='$class'>" . htmlentities($d['label']) . "</option>";
3650    }
3651    //return "<select class='colSortSelect' onchange='window.open(this.value)'>$options</select>";
3652    return "<select class='colSortSelect' onchange='window.location.href=this.value'>$options</select>";
3653}
3654
3655function jqGridColsSorter_GetDefs(string $col_def_key):array {
3656    $function = __FUNCTION__;
3657    $col_def = strit($col_def_key);
3658    $iac_usr_id = strit($_SESSION['usuario_id']);
3659    $colDef = ia_sqlArrayIndx(
3660        "SELECT /*$function*/ jqGridColsSorter_id as value, label, def, para_todos, myDefault, ocultar_ribbon
3661                FROM jqGridColsSorter
3662                WHERE col_def_key = $col_def AND (para_todos = 'Si' OR iac_usr_id = $iac_usr_id)
3663                ORDER BY myDefault, para_todos DESC, jqGridColsSorter.label"
3664    );
3665    foreach($colDef as &$c)
3666        $c['def'] = json_decode($c['def']);
3667    return $colDef;
3668}
3669
3670/**
3671 * Regresa values de string para edit y search options uso:
3672 *  editoptions: {value: "<?php echo units2grid(); ?>"},
3673 *  searchoptions: {value: ":;<?php echo units2grid(); ?>",
3674 *
3675 * @return string
3676 */
3677function units2grid():string {
3678    $u = ia_sqlVector("SELECT /*units2grid*/ CONCAT(unidad, ':', unidad) FROM unidades WHERE vale='Active' ORDER BY unidades_id");
3679    if($u === false)
3680        return "Kg:Kg;Mts:Mts;Pza:Pza;n/a:n/a";
3681    $u[] = 'n/a:n/a';
3682    return implode(";", $u);
3683}
3684
3685function jsCodeColChooser(string $gridId):string {
3686    $savedCode = json_encode(jqGridColsSorter_GetDefs($gridId . '_') );
3687    return <<< JS_CODE
3688    {$gridId}_cols = new jqGridColsSorter(
3689        $("#{$gridId}"), "{$gridId}_", "{$gridId}_cols", [],
3690        $savedCode   
3691    );
3692    {$gridId}_cols.putGetParam();
3693    $("#iactoolbartable").find("TR").first().append("<TD id='putColsChooser'>");
3694    $("#putColsChooser").append({$gridId}_cols.controles());
3695JS_CODE;
3696}
3697
3698function bodega_x_dia_cache($from = null):void {
3699    if(empty($from)) {
3700        $from = min(
3701            Date('Y-m-d', strtotime('2 days ago')),
3702            ia_singleread("SELECT MAX(fecha) FROM bodega_x_dia", "2022-09-01")
3703        );
3704        if(empty($from))
3705            $from = "2022-09-01";
3706    }
3707    $method = __FUNCTION__;
3708    $sql = "
3709    INSERT INTO bodega_x_dia(fecha, bodega, Existencia_Rollos, Existencia_Quantity_Kg, Existencia_Quantity_Mts, Existencia_Containers,Existencia_CIF_Today)
3710    (WITH /* $method */ dia as (SELECT MAX(fecha) as fecha, bodega_id, producto_general_id, color_id
3711        FROM bodega_existencia_diaria
3712        WHERE fecha <= ?
3713        GROUP BY bodega_id, producto_general_id, color_id
3714        HAVING MAX(fecha))
3715     SELECT ?, bed.Bodega,
3716        SUM(bed.existencia_rollos) as Existencia_Rolls,
3717        SUM(IF(u.unidad = 'Kg', bed.existencia_quantity, 0)) as Existencia_Quantity_Kg,
3718        SUM(IF(u.unidad <> 'Kg',0, bed.existencia_quantity)) as Existencia_Quantity_Mts,
3719        SUM(IF(pg.container_quantity = 0, NULL, bed.existencia_quantity / pg.container_quantity)) as Existencia_Containers,
3720        SUM(IFNULL(pcb.cost_cif * bed.existencia_quantity, NULL)) as 'Existencia_CIF_Today'
3721     FROM bodega_existencia_diaria bed
3722              JOIN dia ON bed.fecha = dia.fecha AND bed.bodega_id = dia.bodega_id
3723                    AND bed.producto_general_id = dia.producto_general_id
3724                    AND bed.color_id = dia.color_id
3725      JOIN producto_general pg ON pg.producto_general_id = dia.producto_general_id
3726      LEFT OUTER JOIN unidades u ON u.unidades_id = pg.unidades_id   
3727      LEFT OUTER JOIN producto_costs_bodega pcb ON pcb.producto_general_id = bed.producto_general_id AND pcb.fecha_fin IS NULL
3728     GROUP BY 1, 2) ON DUPLICATE KEY UPDATE 
3729        Existencia_Rollos = VALUES(Existencia_Rollos),
3730        Existencia_Quantity_Kg = VALUES(Existencia_Quantity_Kg),
3731        Existencia_Quantity_Mts = VALUES(Existencia_Quantity_Mts),
3732        Existencia_Containers = VALUES(Existencia_Containers), 
3733        Existencia_CIF_Today = VALUES(Existencia_CIF_Today) ";
3734
3735    global $gSqlClass;
3736    $query = $gSqlClass->mysqli->prepare($sql);
3737    $period = new DatePeriod(
3738        DateTimeImmutable::createFromFormat('Y-m-d', $from),
3739        new DateInterval("P1D"),
3740        new DateTimeImmutable("now")
3741    );
3742    foreach ($period as $date) {
3743        $d2 = $d1 = $date->format('Y-m-d');
3744        $query->bind_param('ss', $d1, $d2);
3745        $query->execute();
3746    }
3747}
3748function bodega_x_dia_ExistenciaMaximaTable($class = 'tblIt'):string {
3749    bodega_x_dia_cache();
3750    $show = "WITH mx AS
3751    (SELECT bodega, MAX(Existencia_Containers) as Existencia_Containers
3752    FROM bodega_x_dia
3753    GROUP BY bodega
3754    )
3755SELECT bd.Bodega, bd.Existencia_Containers as Containers, bd.Existencia_Rollos as Rollos, 
3756    bd.Existencia_Quantity_Kg as Qty_Kg, bd.Existencia_Quantity_Mts Qty_Mts, bd.Fecha
3757 FROM bodega_x_dia bd JOIN mx ON
3758     bd.bodega = mx.bodega AND bd.Existencia_Containers = mx.Existencia_Containers
3759ORDER BY bd.bodega, bd.fecha";
3760    $maxBodega = ia_sqlArray($show, 'Bodega');
3761    return iaTableIt::getTableIt($maxBodega,
3762        "Containers Máximo por Bodega " .
3763        '<i onclick="toExcel(\'tblIt\', \'Existencia_maxima_por_bodega\')" style="color:green;cursor:pointer" class="fa-duotone fa-file-excel"></i>',
3764        tableClass: $class);
3765}
3766
3767function bodega_x_dia_recalc() {
3768    $method = __FUNCTION__;
3769    $last = ia_singleread("SELECT /*$method*/ updated FROM bodega_x_dia_last");
3770    if($last === false) {
3771        ia_query("CREATE /*$method*/ TABLE IF NOT EXISTS bodega_x_dia_last(updated DATETIME NOT NULL )");
3772        ia_query("INSERT /*$method*/ INTO bodega_x_dia_last(updated) VALUES(NOW())");
3773        $last = ia_singleread("SELECT /*$method*/ updated FROM bodega_x_dia_last");
3774    }
3775    if($last === false || Date('Y-m-d H:i', strtotime("2 minutes ago")) < $last )
3776        return;
3777    ia_query("UPDATE /*$method*/ bodega_x_dia_last SET updated=NOW()");
3778    bodega_x_dia_cache('2022-09-01');
3779}
3780