{"id":"cs-1782350865697-67ostpih","status":"done","createdAt":1782350865697,"finishedAt":1782350927950,"meta":{"projectName":"ALimpiezas","projectPath":"C:\\xampp\\htdocs\\ALimpiezas","messagePreview":"Se ha vuelto a cortar twig, css y js","phaseId":null},"events":[{"event":"status","data":{"text":"Modo IDE / sesión — completo (80 herramientas, uso selectivo)"},"at":1782350865700},{"event":"status","data":{"text":"Agente"},"at":1782350865701},{"event":"status","data":{"text":"Modo revisión: snapshot local por archivo"},"at":1782350865701},{"event":"status","data":{"text":"Contexto del IDE cargado"},"at":1782350865702},{"event":"agent","data":{"agentId":"agent-a46adb84-44ba-4df6-8c0b-cf9fe8cc0292","projectPath":"C:\\xampp\\htdocs\\ALimpiezas"},"at":1782350865702},{"event":"changes","data":{"sessionId":"cs-1782350865700-aiddd4","pending":0},"at":1782350865702},{"event":"status","data":{"text":"Procesando…"},"at":1782350865754},{"event":"tool_activity","data":{"activity":{"id":"tool_095f76b9-72fa-45e6-8b6a-71991711b89","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:27:48.904Z"}},"at":1782350868904},{"event":"tool_activity","data":{"activity":{"id":"tool_0d83bbe8-b30e-4f6a-a8a0-8eae9579c6f","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:27:48.906Z"}},"at":1782350868906},{"event":"tool_activity","data":{"activity":{"id":"tool_9d0e92d7-0f51-450b-a528-0645e69f3c1","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:27:48.907Z"}},"at":1782350868907},{"event":"tool_activity","data":{"activity":{"id":"tool_0433f573-6b63-47eb-8f39-f31ee384e1b","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:27:48.908Z"}},"at":1782350868908},{"event":"text","data":{"chunk":"Continúo donde se cortó: reviso el estado de Twig, CSS y JS de confirmación fiscal y completo lo pendiente.\n"},"at":1782350927950},{"event":"tool_activity","data":{"activity":{"id":"tool_0d83bbe8-b30e-4f6a-a8a0-8eae9579c6f","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:27:49.132Z"}},"at":1782350869132},{"event":"tool_activity","data":{"activity":{"id":"tool_095f76b9-72fa-45e6-8b6a-71991711b89","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:27:49.138Z"}},"at":1782350869138},{"event":"tool_activity","data":{"activity":{"id":"tool_0433f573-6b63-47eb-8f39-f31ee384e1b","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:27:49.139Z"}},"at":1782350869139},{"event":"tool_activity","data":{"activity":{"id":"tool_9d0e92d7-0f51-450b-a528-0645e69f3c1","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:27:49.160Z"}},"at":1782350869160},{"event":"tool_activity","data":{"activity":{"id":"tool_8bb959c8-1a65-41de-8a28-828a6972f45","name":"read","status":"running","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:27:50.562Z"}},"at":1782350870562},{"event":"tool_activity","data":{"activity":{"id":"tool_7b6c3aa6-7458-47e4-8263-3bd084a86ab","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:27:50.563Z"}},"at":1782350870563},{"event":"tool_activity","data":{"activity":{"id":"tool_68fb4e6e-102c-4cfe-8709-07e4b2c2fce","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:27:50.564Z"}},"at":1782350870564},{"event":"tool_activity","data":{"activity":{"id":"tool_eddbcea0-346f-452d-ad82-be7a1a32ac4","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:27:50.564Z"}},"at":1782350870564},{"event":"tool_activity","data":{"activity":{"id":"tool_8bb959c8-1a65-41de-8a28-828a6972f45","name":"read","status":"completed","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:27:50.679Z"}},"at":1782350870679},{"event":"tool_activity","data":{"activity":{"id":"tool_7b6c3aa6-7458-47e4-8263-3bd084a86ab","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:27:50.691Z"}},"at":1782350870691},{"event":"tool_activity","data":{"activity":{"id":"tool_68fb4e6e-102c-4cfe-8709-07e4b2c2fce","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:27:50.771Z"}},"at":1782350870771},{"event":"tool_activity","data":{"activity":{"id":"tool_eddbcea0-346f-452d-ad82-be7a1a32ac4","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:27:50.812Z"}},"at":1782350870812},{"event":"tool_activity","data":{"activity":{"id":"tool_9695339c-73e3-46bd-abd7-858c9cb8ab5","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:27:53.027Z"}},"at":1782350873027},{"event":"tool_activity","data":{"activity":{"id":"tool_8551c9c7-5c1a-40db-8b20-99d535270b3","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\MobileConfig.php","detail":"","at":"2026-06-25T01:27:53.125Z"}},"at":1782350873125},{"event":"tool_activity","data":{"activity":{"id":"tool_0c73e880-f347-4132-b8dc-462287e32c1","name":"read","status":"running","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Controller\\MobileExpenses.php","detail":"","at":"2026-06-25T01:27:53.126Z"}},"at":1782350873126},{"event":"tool_activity","data":{"activity":{"id":"tool_9695339c-73e3-46bd-abd7-858c9cb8ab5","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:27:53.180Z"}},"at":1782350873180},{"event":"tool_activity","data":{"activity":{"id":"tool_4f06563b-84f9-4417-a223-e3bf26bf590","name":"read","status":"running","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:27:53.224Z"}},"at":1782350873224},{"event":"tool_activity","data":{"activity":{"id":"tool_8551c9c7-5c1a-40db-8b20-99d535270b3","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\MobileConfig.php","detail":"","at":"2026-06-25T01:27:53.228Z"}},"at":1782350873228},{"event":"tool_activity","data":{"activity":{"id":"tool_0c73e880-f347-4132-b8dc-462287e32c1","name":"read","status":"completed","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Controller\\MobileExpenses.php","detail":"","at":"2026-06-25T01:27:53.231Z"}},"at":1782350873231},{"event":"tool_activity","data":{"activity":{"id":"tool_4f06563b-84f9-4417-a223-e3bf26bf590","name":"read","status":"completed","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:27:53.357Z"}},"at":1782350873357},{"event":"tool_activity","data":{"activity":{"id":"tool_d7e41737-a73b-4350-9167-d895ef76cda","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py","detail":"","at":"2026-06-25T01:27:55.177Z"}},"at":1782350875177},{"event":"tool_activity","data":{"activity":{"id":"tool_2b95a5c3-2b3f-437a-a762-169bbdf51e9","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:27:55.562Z"}},"at":1782350875562},{"event":"tool_activity","data":{"activity":{"id":"tool_6cd48abf-51e9-4a01-9571-a284aa6c3c6","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\docs\\MANUAL-AGENTE-IA.md","detail":"","at":"2026-06-25T01:27:55.687Z"}},"at":1782350875687},{"event":"tool_activity","data":{"activity":{"id":"tool_2b95a5c3-2b3f-437a-a762-169bbdf51e9","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:27:55.748Z"}},"at":1782350875748},{"event":"tool_activity","data":{"activity":{"id":"tool_c4ba1937-2551-4962-96ed-d491cf84a80","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:27:58.664Z"}},"at":1782350878664},{"event":"tool_activity","data":{"activity":{"id":"tool_e47db6b1-aaa9-4851-8945-aa16859a564","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas","detail":"","at":"2026-06-25T01:27:58.664Z"}},"at":1782350878664},{"event":"tool_activity","data":{"activity":{"id":"tool_9c86b91f-c724-42a8-8f5e-4ee45c6a912","name":"read","status":"completed","summary":"read: …htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig","detail":"","at":"2026-06-25T01:27:58.775Z"}},"at":1782350878775},{"event":"tool_activity","data":{"activity":{"id":"tool_c4ba1937-2551-4962-96ed-d491cf84a80","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:27:58.827Z"}},"at":1782350878827},{"event":"tool_activity","data":{"activity":{"id":"tool_e47db6b1-aaa9-4851-8945-aa16859a564","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas","detail":"","at":"2026-06-25T01:27:58.908Z"}},"at":1782350878908},{"event":"tool_activity","data":{"activity":{"id":"tool_fb9803f7-0f02-4f57-8dc1-94a9ddfe17d","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js","detail":"","at":"2026-06-25T01:28:01.151Z"}},"at":1782350881151},{"event":"tool_activity","data":{"activity":{"id":"tool_0f68a6cb-8e61-47c5-9e35-df20d56eb82","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:28:01.151Z"}},"at":1782350881151},{"event":"tool_activity","data":{"activity":{"id":"tool_d437e386-76a5-483b-8334-6f01a10fda5","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:28:01.152Z"}},"at":1782350881152},{"event":"tool_activity","data":{"activity":{"id":"tool_fb9803f7-0f02-4f57-8dc1-94a9ddfe17d","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js","detail":"","at":"2026-06-25T01:28:01.265Z"}},"at":1782350881265},{"event":"tool_activity","data":{"activity":{"id":"tool_0f68a6cb-8e61-47c5-9e35-df20d56eb82","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:28:01.311Z"}},"at":1782350881311},{"event":"tool_activity","data":{"activity":{"id":"tool_d437e386-76a5-483b-8334-6f01a10fda5","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:28:01.366Z"}},"at":1782350881366},{"event":"tool_activity","data":{"activity":{"id":"tool_70d54285-fe16-42f5-8c58-cf72c4ead23","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:28:03.270Z"}},"at":1782350883270},{"event":"tool_activity","data":{"activity":{"id":"tool_be4017a5-bfab-4f13-bdf3-c6869f1295c","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\tools\\bootstrap-plugin.php","detail":"","at":"2026-06-25T01:28:03.378Z"}},"at":1782350883379},{"event":"tool_activity","data":{"activity":{"id":"tool_70d54285-fe16-42f5-8c58-cf72c4ead23","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:28:03.460Z"}},"at":1782350883460},{"event":"tool_activity","data":{"activity":{"id":"tool_ffc9e5b0-9e5c-4d53-9a7e-ddfc5b93633","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:28:13.053Z"}},"at":1782350893053},{"event":"tool_activity","data":{"activity":{"id":"tool_e8f70827-8d94-40c9-9de8-ca53a7820a0","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Model\\AttachedFile.php","detail":"","at":"2026-06-25T01:28:13.206Z"}},"at":1782350893206},{"event":"tool_activity","data":{"activity":{"id":"tool_ffc9e5b0-9e5c-4d53-9a7e-ddfc5b93633","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:28:13.285Z"}},"at":1782350893285},{"event":"tool_activity","data":{"activity":{"id":"tool_baa48100-4ff7-4733-a7ce-c462031bddb","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Core\\Model\\AttachedFile.php","detail":"","at":"2026-06-25T01:28:14.703Z"}},"at":1782350894703},{"event":"tool_activity","data":{"activity":{"id":"tool_69dd0f32-b293-4066-a03c-958edf7fdb9","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Core\\Model\\Base\\AttachedFile.php","detail":"","at":"2026-06-25T01:28:14.807Z"}},"at":1782350894807},{"event":"tool_activity","data":{"activity":{"id":"tool_baa48100-4ff7-4733-a7ce-c462031bddb","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Core\\Model\\AttachedFile.php","detail":"","at":"2026-06-25T01:28:14.844Z"}},"at":1782350894844},{"event":"tool_activity","data":{"activity":{"id":"tool_70fdb96c-ad8a-49fa-88ca-8f1999762f0","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Core\\Model\\AttachedFile.php","detail":"","at":"2026-06-25T01:28:16.102Z"}},"at":1782350896102},{"event":"tool_activity","data":{"activity":{"id":"tool_b61cb384-e13d-43a1-b74d-efbff654859","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Model\\AttachedFile.php","detail":"","at":"2026-06-25T01:28:20.259Z"}},"at":1782350900259},{"event":"tool_activity","data":{"activity":{"id":"tool_a9ccd7ad-d3fe-4c97-9bed-c4c02e3064c","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Core\\Model\\AttachedFile.php","detail":"","at":"2026-06-25T01:28:26.936Z"}},"at":1782350906936},{"event":"tool_activity","data":{"activity":{"id":"tool_22d8cfba-a1bc-45b1-ab9d-34b35c02e86","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Core\\Model\\AttachedFile.php","detail":"","at":"2026-06-25T01:28:29.834Z"}},"at":1782350909834},{"event":"tool_activity","data":{"activity":{"id":"tool_e8eb3fd4-eb0d-498f-9cbf-87100b3da25","name":"edit","status":"running","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:28:32.702Z"}},"at":1782350912702},{"event":"tool_activity","data":{"activity":{"id":"tool_e9373c60-9c57-422e-91ec-a181bab8421","name":"edit","status":"running","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:28:32.713Z"}},"at":1782350912713},{"event":"text","data":{"chunk":"Completando la integración JS con la UI fiscal del Twig y desplegando los assets.\n"},"at":1782350927950},{"event":"tool_activity","data":{"activity":{"id":"tool_e9373c60-9c57-422e-91ec-a181bab8421","name":"edit","status":"running","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:28:32.914Z"}},"at":1782350912914},{"event":"tool_activity","data":{"activity":{"id":"tool_e8eb3fd4-eb0d-498f-9cbf-87100b3da25","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:28:32.925Z"}},"at":1782350912925},{"event":"file_change","data":{"sessionId":"cs-1782350865700-aiddd4","change":{"id":"ch-1","path":"Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js\n@@ -4,7 +4,7 @@\n     var confirmModal = null;\n     var confirmModalEl = null;\n     var expenseMode = (window.ALIMPIEZAS_MOBILE && window.ALIMPIEZAS_MOBILE.expenseMode) || 'ocr';\n-    var JS_VERSION = '2026-06-25d';\n+    var JS_VERSION = '2026-06-25e';\n     var selectedFiles = [];\n     var fileIdCounter = 0;\n ","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js</div><div class=\"diff-line diff-hunk\">@@ -4,7 +4,7 @@</div><div class=\"diff-line diff-ctx\">     var confirmModal = null;</div><div class=\"diff-line diff-ctx\">     var confirmModalEl = null;</div><div class=\"diff-line diff-ctx\">     var expenseMode = (window.ALIMPIEZAS_MOBILE &amp;&amp; window.ALIMPIEZAS_MOBILE.expenseMode) || 'ocr';</div><div class=\"diff-line diff-del\">-    var JS_VERSION = '2026-06-25d';</div><div class=\"diff-line diff-add\">+    var JS_VERSION = '2026-06-25e';</div><div class=\"diff-line diff-ctx\">     var selectedFiles = [];</div><div class=\"diff-line diff-ctx\">     var fileIdCounter = 0;</div><div class=\"diff-line diff-ctx\"> </div></div>","linesAdded":1,"linesRemoved":1,"status":"pending"},"pending":1},"at":1782350912925},{"event":"tool_activity","data":{"activity":{"id":"tool_e5a8632d-b6e3-4999-a7f1-f48d3d543c3","name":"edit","status":"running","summary":"edit: …htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig","detail":"","at":"2026-06-25T01:28:33.114Z"}},"at":1782350913114},{"event":"tool_activity","data":{"activity":{"id":"tool_e9373c60-9c57-422e-91ec-a181bab8421","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:28:33.129Z"}},"at":1782350913129},{"event":"file_change","data":{"sessionId":"cs-1782350865700-aiddd4","change":{"id":"ch-2","path":"Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php\n@@ -125,7 +125,9 @@\n         $mode = MobileConfig::expenseMode();\n         $serviceUrl = MobileConfig::aiServiceUrl();\n         $service = MobileConfig::isAiServiceEnabled() ? self::checkServiceHealth() : ['ok' => true, 'skipped' => true];\n-        $ollama = $mode === MobileConfig::MODE_AI ? self::checkOllamaHealth() : ['ok' => null, 'skipped' => true];\n+        $ollama = MobileConfig::isAiServiceEnabled()\n+            ? self::checkOllamaHealth()\n+            : ['ok' => null, 'skipped' => true];\n \n         return [\n             'ok' => true,","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php</div><div class=\"diff-line diff-hunk\">@@ -125,7 +125,9 @@</div><div class=\"diff-line diff-ctx\">         $mode = MobileConfig::expenseMode();</div><div class=\"diff-line diff-ctx\">         $serviceUrl = MobileConfig::aiServiceUrl();</div><div class=\"diff-line diff-ctx\">         $service = MobileConfig::isAiServiceEnabled() ? self::checkServiceHealth() : ['ok' =&gt; true, 'skipped' =&gt; true];</div><div class=\"diff-line diff-del\">-        $ollama = $mode === MobileConfig::MODE_AI ? self::checkOllamaHealth() : ['ok' =&gt; null, 'skipped' =&gt; true];</div><div class=\"diff-line diff-add\">+        $ollama = MobileConfig::isAiServiceEnabled()</div><div class=\"diff-line diff-add\">+            ? self::checkOllamaHealth()</div><div class=\"diff-line diff-add\">+            : ['ok' =&gt; null, 'skipped' =&gt; true];</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">         return [</div><div class=\"diff-line diff-ctx\">             'ok' =&gt; true,</div></div>","linesAdded":3,"linesRemoved":1,"status":"pending"},"pending":2},"at":1782350913129},{"event":"tool_activity","data":{"activity":{"id":"tool_f5664b19-1aca-48bd-9774-f75321d4dae","name":"edit","status":"running","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js","detail":"","at":"2026-06-25T01:28:33.227Z"}},"at":1782350913227},{"event":"tool_activity","data":{"activity":{"id":"tool_e5a8632d-b6e3-4999-a7f1-f48d3d543c3","name":"edit","status":"completed","summary":"edit: …htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig","detail":"","at":"2026-06-25T01:28:33.350Z"}},"at":1782350913350},{"event":"file_change","data":{"sessionId":"cs-1782350865700-aiddd4","change":{"id":"ch-3","path":"Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig\n@@ -14,7 +14,7 @@\n     <title>{{ fsc.title }}</title>\n     <link rel=\"stylesheet\" href=\"{{ asset('node_modules/bootstrap/dist/css/bootstrap.min.css') }}\">\n     <link rel=\"stylesheet\" href=\"{{ asset('node_modules/@fortawesome/fontawesome-free/css/all.min.css') }}\">\n-    <link rel=\"stylesheet\" href=\"{{ asset('Dinamic/Assets/CSS/mobile.css') }}?v=20260625d\">\n+    <link rel=\"stylesheet\" href=\"{{ asset('Dinamic/Assets/CSS/mobile.css') }}?v=20260625e\">\n     {% block head %}{% endblock %}\n </head>\n <body class=\"mobile-app\">\n@@ -67,11 +67,11 @@\n             expenseMode: {{ settings('alimpiezas', 'expense_mode', 'ocr')|json_encode|raw }}\n         };\n     </script>\n-    <script src=\"{{ asset('Dinamic/Assets/JS/help-guide.js') }}?v=20260625d\"></script>\n-    <script src=\"{{ asset('Dinamic/Assets/JS/mobile.js') }}?v=20260625d\"></script>\n+    <script src=\"{{ asset('Dinamic/Assets/JS/help-guide.js') }}?v=20260625e\"></script>\n+    <script src=\"{{ asset('Dinamic/Assets/JS/mobile.js') }}?v=20260625e\"></script>\n     <script>\n         if ('serviceWorker' in navigator) {\n-            navigator.serviceWorker.register('{{ asset('Dinamic/Assets/sw.js') }}?v=20260625d')\n+            navigator.serviceWorker.register('{{ asset('Dinamic/Assets/sw.js') }}?v=20260625e')\n                 .then(function (reg) {\n                     if (reg.waiting) {\n                         reg.waiting.postMessage({ type: 'SKIP_WAITING' });","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig</div><div class=\"diff-line diff-hunk\">@@ -14,7 +14,7 @@</div><div class=\"diff-line diff-ctx\">     &lt;title&gt;{{ fsc.title }}&lt;/title&gt;</div><div class=\"diff-line diff-ctx\">     &lt;link rel=\"stylesheet\" href=\"{{ asset('node_modules/bootstrap/dist/css/bootstrap.min.css') }}\"&gt;</div><div class=\"diff-line diff-ctx\">     &lt;link rel=\"stylesheet\" href=\"{{ asset('node_modules/@fortawesome/fontawesome-free/css/all.min.css') }}\"&gt;</div><div class=\"diff-line diff-del\">-    &lt;link rel=\"stylesheet\" href=\"{{ asset('Dinamic/Assets/CSS/mobile.css') }}?v=20260625d\"&gt;</div><div class=\"diff-line diff-add\">+    &lt;link rel=\"stylesheet\" href=\"{{ asset('Dinamic/Assets/CSS/mobile.css') }}?v=20260625e\"&gt;</div><div class=\"diff-line diff-ctx\">     {% block head %}{% endblock %}</div><div class=\"diff-line diff-ctx\"> &lt;/head&gt;</div><div class=\"diff-line diff-ctx\"> &lt;body class=\"mobile-app\"&gt;</div><div class=\"diff-line diff-hunk\">@@ -67,11 +67,11 @@</div><div class=\"diff-line diff-ctx\">             expenseMode: {{ settings('alimpiezas', 'expense_mode', 'ocr')|json_encode|raw }}</div><div class=\"diff-line diff-ctx\">         };</div><div class=\"diff-line diff-ctx\">     &lt;/script&gt;</div><div class=\"diff-line diff-del\">-    &lt;script src=\"{{ asset('Dinamic/Assets/JS/help-guide.js') }}?v=20260625d\"&gt;&lt;/script&gt;</div><div class=\"diff-line diff-del\">-    &lt;script src=\"{{ asset('Dinamic/Assets/JS/mobile.js') }}?v=20260625d\"&gt;&lt;/script&gt;</div><div class=\"diff-line diff-add\">+    &lt;script src=\"{{ asset('Dinamic/Assets/JS/help-guide.js') }}?v=20260625e\"&gt;&lt;/script&gt;</div><div class=\"diff-line diff-add\">+    &lt;script src=\"{{ asset('Dinamic/Assets/JS/mobile.js') }}?v=20260625e\"&gt;&lt;/script&gt;</div><div class=\"diff-line diff-ctx\">     &lt;script&gt;</div><div class=\"diff-line diff-ctx\">         if ('serviceWorker' in navigator) {</div><div class=\"diff-line diff-del\">-            navigator.serviceWorker.register('{{ asset('Dinamic/Assets/sw.js') }}?v=20260625d')</div><div class=\"diff-line diff-add\">+            navigator.serviceWorker.register('{{ asset('Dinamic/Assets/sw.js') }}?v=20260625e')</div><div class=\"diff-line diff-ctx\">                 .then(function (reg) {</div><div class=\"diff-line diff-ctx\">                     if (reg.waiting) {</div><div class=\"diff-line diff-ctx\">                         reg.waiting.postMessage({ type: 'SKIP_WAITING' });</div></div>","linesAdded":4,"linesRemoved":4,"status":"pending"},"pending":3},"at":1782350913350},{"event":"tool_activity","data":{"activity":{"id":"tool_f5664b19-1aca-48bd-9774-f75321d4dae","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js","detail":"","at":"2026-06-25T01:28:33.485Z"}},"at":1782350913485},{"event":"file_change","data":{"sessionId":"cs-1782350865700-aiddd4","change":{"id":"ch-4","path":"Plugins\\ALimpiezasMobile\\Assets\\sw.js","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js\n@@ -1,4 +1,4 @@\n-const CACHE_NAME = 'alimpiezas-mobile-v4';\n+const CACHE_NAME = 'alimpiezas-mobile-v5';\n const CORE_ASSETS = [\n     '/ALimpiezas/Dinamic/Assets/CSS/mobile.css',\n     '/ALimpiezas/Dinamic/Assets/JS/mobile.js',","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js</div><div class=\"diff-line diff-hunk\">@@ -1,4 +1,4 @@</div><div class=\"diff-line diff-del\">-const CACHE_NAME = 'alimpiezas-mobile-v4';</div><div class=\"diff-line diff-add\">+const CACHE_NAME = 'alimpiezas-mobile-v5';</div><div class=\"diff-line diff-ctx\"> const CORE_ASSETS = [</div><div class=\"diff-line diff-ctx\">     '/ALimpiezas/Dinamic/Assets/CSS/mobile.css',</div><div class=\"diff-line diff-ctx\">     '/ALimpiezas/Dinamic/Assets/JS/mobile.js',</div></div>","linesAdded":1,"linesRemoved":1,"status":"pending"},"pending":4},"at":1782350913486},{"event":"tool_activity","data":{"activity":{"id":"tool_61915eb0-7c78-4dc5-affa-bfa2522f30a","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:28:44.186Z"}},"at":1782350924186},{"event":"file_change","data":{"sessionId":"cs-1782350865700-aiddd4","change":{"id":"ch-5","path":"Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js\n@@ -359,49 +359,204 @@\n         }\n     }\n \n-    function fillConfirmForm(item) {\n-        document.getElementById('confirm-queue-id').value = item.id;\n-        document.getElementById('cf-supplier-name').value = item.supplier_name || '';\n-        document.getElementById('cf-supplier-cif').value = item.supplier_cif || '';\n-        document.getElementById('cf-invoice-number').value = item.invoice_number || '';\n-        document.getElementById('cf-invoice-date').value = item.invoice_date || '';\n-        document.getElementById('cf-base').value = item.base_amount || '';\n-        document.getElementById('cf-tax').value = item.tax_amount || '';\n-        document.getElementById('cf-total').value = item.total_amount || '';\n+    function setConfirmField(id, value) {\n+        var el = document.getElementById(id);\n+        if (!el) {\n+            return;\n+        }\n+        if (value === null || value === undefined) {\n+            el.value = '';\n+            return;\n+        }\n+        el.value = value;\n+    }\n \n-        var notes = document.getElementById('cf-notes');\n-        var noteText = '';\n-        var alertClass = 'alert-info';\n+    function setSelectValue(id, value) {\n+        var el = document.getElementById(id);\n+        if (!el || value === null || value === undefined || value === '') {\n+            return;\n+        }\n+        var normalized = String(Math.round(parseFloat(value)));\n+        if (el.querySelector('option[value=\"' + normalized + '\"]')) {\n+            el.value = normalized;\n+        }\n+    }\n \n-        if (item.ocr_engine === 'manual') {\n-            noteText = 'Modo manual: verifica todos los campos antes de confirmar.';\n-        } else {\n-            noteText = 'Confianza: ' + Math.round((item.confidence || 0) * 100) + '%';\n-            if (item.ocr_engine) {\n-                noteText += ' · Motor: ' + item.ocr_engine;\n+    function documentKindLabel(kind, docType) {\n+        var labels = {\n+            expense_received: 'Factura recibida / gasto',\n+            issued_invoice: 'Factura emitida',\n+            simplified_ticket: 'Ticket simplificado',\n+            proforma: 'Proforma',\n+            unknown: 'Documento contable'\n+        };\n+        if (labels[kind]) {\n+            return labels[kind];\n+        }\n+        return docType === 'issued' ? 'Factura emitida' : 'Factura recibida / gasto';\n+    }\n+\n+    function engineLabel(engine) {\n+        if (!engine || engine === 'manual') {\n+            return 'Entrada manual';\n+        }\n+        return String(engine).replace(/\\+/g, ' + ').replace(/ollama/i, 'Ollama');\n+    }\n+\n+    function applyFieldStatus(fieldStatus) {\n+        document.querySelectorAll('.fiscal-field').forEach(function (field) {\n+            field.classList.remove('fiscal-field--ok', 'fiscal-field--review', 'fiscal-field--missing');\n+            var key = field.getAttribute('data-field');\n+            var status = fieldStatus && fieldStatus[key];\n+            if (status === 'ok') {\n+                field.classList.add('fiscal-field--ok');\n+            } else if (status === 'review') {\n+                field.classList.add('fiscal-field--review');\n+            } else if (status === 'missing') {\n+                field.classList.add('fiscal-field--missing');\n             }\n-            if ((item.confidence || 0) < 0.4) {\n-                alertClass = 'alert-warning';\n+        });\n+    }\n+\n+    function renderFiscalAlerts(item) {\n+        var container = document.getElementById('cf-alerts');\n+        if (!container) {\n+            return;\n+        }\n+\n+        container.innerHTML = '';\n+        var errors = item.validation_errors || [];\n+        var warnings = item.validation_warnings || item.warnings || [];\n+\n+        errors.forEach(function (msg) {\n+            var row = document.createElement('div');\n+            row.className = 'fiscal-alert-item fiscal-alert-error';\n+            row.innerHTML = '<i class=\"fa-solid fa-circle-xmark me-2\"></i>' + msg;\n+            container.appendChild(row);\n+        });\n+\n+        warnings.forEach(function (msg) {\n+            var row = document.createElement('div');\n+            row.className = 'fiscal-alert-item fiscal-alert-warning';\n+            row.innerHTML = '<i class=\"fa-solid fa-triangle-exclamation me-2\"></i>' + msg;\n+            container.appendChild(row);\n+        });\n+    }\n+\n+    function renderComplianceBar(item) {\n+        var compliance = item.fiscal_compliance;\n+        if (compliance === null || compliance === undefined) {\n+            compliance = item.confidence || 0;\n+        }\n+\n+        var pct = Math.round(Number(compliance) * 100);\n+        var fill = document.getElementById('cf-compliance-fill');\n+        var pctEl = document.getElementById('cf-compliance-pct');\n+        var confText = document.getElementById('cf-confidence-text');\n+        var wrap = document.getElementById('cf-compliance-wrap');\n+\n+        if (fill) {\n+            fill.style.width = Math.max(0, Math.min(100, pct)) + '%';\n+            fill.classList.remove('compliance-high', 'compliance-mid', 'compliance-low');\n+            if (pct >= 80) {\n+                fill.classList.add('compliance-high');\n+            } else if (pct >= 50) {\n+                fill.classList.add('compliance-mid');\n+            } else {\n+                fill.classList.add('compliance-low');\n             }\n         }\n \n-        if (item.warnings && item.warnings.length) {\n-            alertClass = 'alert-warning';\n-            noteText += '\\n' + item.warnings.join(' · ');\n+        if (pctEl) {\n+            pctEl.textContent = pct + '%';\n+            pctEl.className = 'small fw-semibold ' + (pct >= 80 ? 'text-success' : pct >= 50 ? 'text-warning' : 'text-danger');\n         }\n-        if (item.notes) {\n-            noteText += '\\n' + item.notes;\n+\n+        if (confText) {\n+            confText.textContent = 'Motor: ' + engineLabel(item.ocr_engine)\n+                + ' · Confianza extracción: ' + Math.round((item.confidence || 0) * 100) + '%';\n         }\n \n-        notes.className = 'alert mt-3 small mb-0 ' + alertClass;\n-        notes.textContent = noteText;\n+        if (wrap) {\n+            wrap.classList.toggle('compliance-ready', pct >= 80);\n+            wrap.classList.toggle('compliance-review', pct >= 50 && pct < 80);\n+            wrap.classList.toggle('compliance-risk', pct < 50);\n+        }\n+    }\n \n+    function fillConfirmForm(item) {\n+        setConfirmField('confirm-queue-id', item.id);\n+        setConfirmField('cf-doc-type', item.doc_type || 'expense');\n+        setConfirmField('cf-supplier-name', item.supplier_name);\n+        setConfirmField('cf-supplier-cif', item.supplier_cif);\n+        setConfirmField('cf-address', item.counterparty_address);\n+        setConfirmField('cf-invoice-series', item.invoice_series);\n+        setConfirmField('cf-invoice-number', item.invoice_number);\n+        setConfirmField('cf-invoice-date', item.invoice_date);\n+        setConfirmField('cf-operation-date', item.operation_date || item.invoice_date);\n+        setConfirmField('cf-concept', item.concept);\n+        setConfirmField('cf-base', item.base_amount);\n+        setConfirmField('cf-tax', item.tax_amount);\n+        setConfirmField('cf-total', item.total_amount);\n+        setConfirmField('cf-irpf-amount', item.irpf_amount);\n+        setSelectValue('cf-tax-rate', item.tax_rate);\n+        setSelectValue('cf-irpf-rate', item.irpf_rate);\n+\n+        var deductible = document.getElementById('cf-deductible');\n+        if (deductible) {\n+            deductible.checked = item.deductible !== false && item.doc_type !== 'issued';\n+        }\n+\n+        var rectifying = document.getElementById('cf-rectifying');\n+        if (rectifying) {\n+            rectifying.checked = !!item.is_rectifying;\n+        }\n+\n+        var docBadge = document.getElementById('cf-doc-kind-badge');\n+        if (docBadge) {\n+            docBadge.textContent = documentKindLabel(item.document_kind, item.doc_type);\n+            docBadge.className = 'badge fiscal-badge-doc' + (item.doc_type === 'issued' ? ' fiscal-badge-doc--issued' : '');\n+        }\n+\n+        var engineBadge = document.getElementById('cf-engine-badge');\n+        if (engineBadge) {\n+            engineBadge.textContent = engineLabel(item.ocr_engine);\n+            engineBadge.className = 'badge fiscal-badge-engine'\n+                + (String(item.ocr_engine || '').indexOf('ollama') >= 0 ? ' fiscal-badge-engine--ai' : '');\n+        }\n+\n         var preview = document.getElementById('confirm-preview-image');\n-        if (item.image_url) {\n-            preview.innerHTML = '<img src=\"' + item.image_url + '\" class=\"img-fluid rounded\" alt=\"Documento\">';\n-        } else {\n-            preview.innerHTML = '';\n+        if (preview) {\n+            if (item.image_url) {\n+                preview.innerHTML = '<img src=\"' + item.image_url + '\" class=\"img-fluid rounded w-100\" alt=\"Documento escaneado\">';\n+            } else {\n+                preview.innerHTML = '<div class=\"fiscal-preview-empty\"><i class=\"fa-solid fa-file-invoice fa-2x mb-2\"></i><div class=\"small\">Sin vista previa del documento</div></div>';\n+            }\n         }\n+\n+        var notes = document.getElementById('cf-notes');\n+        if (notes) {\n+            var noteText = item.notes || 'Revisa los datos antes de confirmar.';\n+            if (item.ocr_engine === 'manual') {\n+                noteText = 'Modo manual: verifica todos los campos obligatorios (RD 1619/2012).';\n+            }\n+\n+            notes.textContent = noteText;\n+            notes.className = 'alert alert-light border mt-3 small mb-0';\n+\n+            var compliance = item.fiscal_compliance ?? item.confidence ?? 0;\n+            if ((item.validation_errors || []).length > 0) {\n+                notes.className = 'alert alert-danger mt-3 small mb-0';\n+            } else if ((item.validation_warnings || item.warnings || []).length > 0 || compliance < 0.7) {\n+                notes.className = 'alert alert-warning mt-3 small mb-0';\n+            } else if (compliance >= 0.85) {\n+                notes.className = 'alert alert-success mt-3 small mb-0';\n+            }\n+        }\n+\n+        applyFieldStatus(item.field_status || {});\n+        renderFiscalAlerts(item);\n+        renderComplianceBar(item);\n     }\n \n     function blurActiveElement() {","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js</div><div class=\"diff-line diff-hunk\">@@ -359,49 +359,204 @@</div><div class=\"diff-line diff-ctx\">         }</div><div class=\"diff-line diff-ctx\">     }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-del\">-    function fillConfirmForm(item) {</div><div class=\"diff-line diff-del\">-        document.getElementById('confirm-queue-id').value = item.id;</div><div class=\"diff-line diff-del\">-        document.getElementById('cf-supplier-name').value = item.supplier_name || '';</div><div class=\"diff-line diff-del\">-        document.getElementById('cf-supplier-cif').value = item.supplier_cif || '';</div><div class=\"diff-line diff-del\">-        document.getElementById('cf-invoice-number').value = item.invoice_number || '';</div><div class=\"diff-line diff-del\">-        document.getElementById('cf-invoice-date').value = item.invoice_date || '';</div><div class=\"diff-line diff-del\">-        document.getElementById('cf-base').value = item.base_amount || '';</div><div class=\"diff-line diff-del\">-        document.getElementById('cf-tax').value = item.tax_amount || '';</div><div class=\"diff-line diff-del\">-        document.getElementById('cf-total').value = item.total_amount || '';</div><div class=\"diff-line diff-add\">+    function setConfirmField(id, value) {</div><div class=\"diff-line diff-add\">+        var el = document.getElementById(id);</div><div class=\"diff-line diff-add\">+        if (!el) {</div><div class=\"diff-line diff-add\">+            return;</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+        if (value === null || value === undefined) {</div><div class=\"diff-line diff-add\">+            el.value = '';</div><div class=\"diff-line diff-add\">+            return;</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+        el.value = value;</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-del\">-        var notes = document.getElementById('cf-notes');</div><div class=\"diff-line diff-del\">-        var noteText = '';</div><div class=\"diff-line diff-del\">-        var alertClass = 'alert-info';</div><div class=\"diff-line diff-add\">+    function setSelectValue(id, value) {</div><div class=\"diff-line diff-add\">+        var el = document.getElementById(id);</div><div class=\"diff-line diff-add\">+        if (!el || value === null || value === undefined || value === '') {</div><div class=\"diff-line diff-add\">+            return;</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+        var normalized = String(Math.round(parseFloat(value)));</div><div class=\"diff-line diff-add\">+        if (el.querySelector('option[value=\"' + normalized + '\"]')) {</div><div class=\"diff-line diff-add\">+            el.value = normalized;</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-del\">-        if (item.ocr_engine === 'manual') {</div><div class=\"diff-line diff-del\">-            noteText = 'Modo manual: verifica todos los campos antes de confirmar.';</div><div class=\"diff-line diff-del\">-        } else {</div><div class=\"diff-line diff-del\">-            noteText = 'Confianza: ' + Math.round((item.confidence || 0) * 100) + '%';</div><div class=\"diff-line diff-del\">-            if (item.ocr_engine) {</div><div class=\"diff-line diff-del\">-                noteText += ' · Motor: ' + item.ocr_engine;</div><div class=\"diff-line diff-add\">+    function documentKindLabel(kind, docType) {</div><div class=\"diff-line diff-add\">+        var labels = {</div><div class=\"diff-line diff-add\">+            expense_received: 'Factura recibida / gasto',</div><div class=\"diff-line diff-add\">+            issued_invoice: 'Factura emitida',</div><div class=\"diff-line diff-add\">+            simplified_ticket: 'Ticket simplificado',</div><div class=\"diff-line diff-add\">+            proforma: 'Proforma',</div><div class=\"diff-line diff-add\">+            unknown: 'Documento contable'</div><div class=\"diff-line diff-add\">+        };</div><div class=\"diff-line diff-add\">+        if (labels[kind]) {</div><div class=\"diff-line diff-add\">+            return labels[kind];</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+        return docType === 'issued' ? 'Factura emitida' : 'Factura recibida / gasto';</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+    function engineLabel(engine) {</div><div class=\"diff-line diff-add\">+        if (!engine || engine === 'manual') {</div><div class=\"diff-line diff-add\">+            return 'Entrada manual';</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+        return String(engine).replace(/\\+/g, ' + ').replace(/ollama/i, 'Ollama');</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+    function applyFieldStatus(fieldStatus) {</div><div class=\"diff-line diff-add\">+        document.querySelectorAll('.fiscal-field').forEach(function (field) {</div><div class=\"diff-line diff-add\">+            field.classList.remove('fiscal-field--ok', 'fiscal-field--review', 'fiscal-field--missing');</div><div class=\"diff-line diff-add\">+            var key = field.getAttribute('data-field');</div><div class=\"diff-line diff-add\">+            var status = fieldStatus &amp;&amp; fieldStatus[key];</div><div class=\"diff-line diff-add\">+            if (status === 'ok') {</div><div class=\"diff-line diff-add\">+                field.classList.add('fiscal-field--ok');</div><div class=\"diff-line diff-add\">+            } else if (status === 'review') {</div><div class=\"diff-line diff-add\">+                field.classList.add('fiscal-field--review');</div><div class=\"diff-line diff-add\">+            } else if (status === 'missing') {</div><div class=\"diff-line diff-add\">+                field.classList.add('fiscal-field--missing');</div><div class=\"diff-line diff-ctx\">             }</div><div class=\"diff-line diff-del\">-            if ((item.confidence || 0) &lt; 0.4) {</div><div class=\"diff-line diff-del\">-                alertClass = 'alert-warning';</div><div class=\"diff-line diff-add\">+        });</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+    function renderFiscalAlerts(item) {</div><div class=\"diff-line diff-add\">+        var container = document.getElementById('cf-alerts');</div><div class=\"diff-line diff-add\">+        if (!container) {</div><div class=\"diff-line diff-add\">+            return;</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        container.innerHTML = '';</div><div class=\"diff-line diff-add\">+        var errors = item.validation_errors || [];</div><div class=\"diff-line diff-add\">+        var warnings = item.validation_warnings || item.warnings || [];</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        errors.forEach(function (msg) {</div><div class=\"diff-line diff-add\">+            var row = document.createElement('div');</div><div class=\"diff-line diff-add\">+            row.className = 'fiscal-alert-item fiscal-alert-error';</div><div class=\"diff-line diff-add\">+            row.innerHTML = '&lt;i class=\"fa-solid fa-circle-xmark me-2\"&gt;&lt;/i&gt;' + msg;</div><div class=\"diff-line diff-add\">+            container.appendChild(row);</div><div class=\"diff-line diff-add\">+        });</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        warnings.forEach(function (msg) {</div><div class=\"diff-line diff-add\">+            var row = document.createElement('div');</div><div class=\"diff-line diff-add\">+            row.className = 'fiscal-alert-item fiscal-alert-warning';</div><div class=\"diff-line diff-add\">+            row.innerHTML = '&lt;i class=\"fa-solid fa-triangle-exclamation me-2\"&gt;&lt;/i&gt;' + msg;</div><div class=\"diff-line diff-add\">+            container.appendChild(row);</div><div class=\"diff-line diff-add\">+        });</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+    function renderComplianceBar(item) {</div><div class=\"diff-line diff-add\">+        var compliance = item.fiscal_compliance;</div><div class=\"diff-line diff-add\">+        if (compliance === null || compliance === undefined) {</div><div class=\"diff-line diff-add\">+            compliance = item.confidence || 0;</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        var pct = Math.round(Number(compliance) * 100);</div><div class=\"diff-line diff-add\">+        var fill = document.getElementById('cf-compliance-fill');</div><div class=\"diff-line diff-add\">+        var pctEl = document.getElementById('cf-compliance-pct');</div><div class=\"diff-line diff-add\">+        var confText = document.getElementById('cf-confidence-text');</div><div class=\"diff-line diff-add\">+        var wrap = document.getElementById('cf-compliance-wrap');</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        if (fill) {</div><div class=\"diff-line diff-add\">+            fill.style.width = Math.max(0, Math.min(100, pct)) + '%';</div><div class=\"diff-line diff-add\">+            fill.classList.remove('compliance-high', 'compliance-mid', 'compliance-low');</div><div class=\"diff-line diff-add\">+            if (pct &gt;= 80) {</div><div class=\"diff-line diff-add\">+                fill.classList.add('compliance-high');</div><div class=\"diff-line diff-add\">+            } else if (pct &gt;= 50) {</div><div class=\"diff-line diff-add\">+                fill.classList.add('compliance-mid');</div><div class=\"diff-line diff-add\">+            } else {</div><div class=\"diff-line diff-add\">+                fill.classList.add('compliance-low');</div><div class=\"diff-line diff-ctx\">             }</div><div class=\"diff-line diff-ctx\">         }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-del\">-        if (item.warnings &amp;&amp; item.warnings.length) {</div><div class=\"diff-line diff-del\">-            alertClass = 'alert-warning';</div><div class=\"diff-line diff-del\">-            noteText += '\\n' + item.warnings.join(' · ');</div><div class=\"diff-line diff-add\">+        if (pctEl) {</div><div class=\"diff-line diff-add\">+            pctEl.textContent = pct + '%';</div><div class=\"diff-line diff-add\">+            pctEl.className = 'small fw-semibold ' + (pct &gt;= 80 ? 'text-success' : pct &gt;= 50 ? 'text-warning' : 'text-danger');</div><div class=\"diff-line diff-ctx\">         }</div><div class=\"diff-line diff-del\">-        if (item.notes) {</div><div class=\"diff-line diff-del\">-            noteText += '\\n' + item.notes;</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        if (confText) {</div><div class=\"diff-line diff-add\">+            confText.textContent = 'Motor: ' + engineLabel(item.ocr_engine)</div><div class=\"diff-line diff-add\">+                + ' · Confianza extracción: ' + Math.round((item.confidence || 0) * 100) + '%';</div><div class=\"diff-line diff-ctx\">         }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-del\">-        notes.className = 'alert mt-3 small mb-0 ' + alertClass;</div><div class=\"diff-line diff-del\">-        notes.textContent = noteText;</div><div class=\"diff-line diff-add\">+        if (wrap) {</div><div class=\"diff-line diff-add\">+            wrap.classList.toggle('compliance-ready', pct &gt;= 80);</div><div class=\"diff-line diff-add\">+            wrap.classList.toggle('compliance-review', pct &gt;= 50 &amp;&amp; pct &lt; 80);</div><div class=\"diff-line diff-add\">+            wrap.classList.toggle('compliance-risk', pct &lt; 50);</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-add\">+    function fillConfirmForm(item) {</div><div class=\"diff-line diff-add\">+        setConfirmField('confirm-queue-id', item.id);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-doc-type', item.doc_type || 'expense');</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-supplier-name', item.supplier_name);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-supplier-cif', item.supplier_cif);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-address', item.counterparty_address);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-invoice-series', item.invoice_series);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-invoice-number', item.invoice_number);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-invoice-date', item.invoice_date);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-operation-date', item.operation_date || item.invoice_date);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-concept', item.concept);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-base', item.base_amount);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-tax', item.tax_amount);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-total', item.total_amount);</div><div class=\"diff-line diff-add\">+        setConfirmField('cf-irpf-amount', item.irpf_amount);</div><div class=\"diff-line diff-add\">+        setSelectValue('cf-tax-rate', item.tax_rate);</div><div class=\"diff-line diff-add\">+        setSelectValue('cf-irpf-rate', item.irpf_rate);</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        var deductible = document.getElementById('cf-deductible');</div><div class=\"diff-line diff-add\">+        if (deductible) {</div><div class=\"diff-line diff-add\">+            deductible.checked = item.deductible !== false &amp;&amp; item.doc_type !== 'issued';</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        var rectifying = document.getElementById('cf-rectifying');</div><div class=\"diff-line diff-add\">+        if (rectifying) {</div><div class=\"diff-line diff-add\">+            rectifying.checked = !!item.is_rectifying;</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        var docBadge = document.getElementById('cf-doc-kind-badge');</div><div class=\"diff-line diff-add\">+        if (docBadge) {</div><div class=\"diff-line diff-add\">+            docBadge.textContent = documentKindLabel(item.document_kind, item.doc_type);</div><div class=\"diff-line diff-add\">+            docBadge.className = 'badge fiscal-badge-doc' + (item.doc_type === 'issued' ? ' fiscal-badge-doc--issued' : '');</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        var engineBadge = document.getElementById('cf-engine-badge');</div><div class=\"diff-line diff-add\">+        if (engineBadge) {</div><div class=\"diff-line diff-add\">+            engineBadge.textContent = engineLabel(item.ocr_engine);</div><div class=\"diff-line diff-add\">+            engineBadge.className = 'badge fiscal-badge-engine'</div><div class=\"diff-line diff-add\">+                + (String(item.ocr_engine || '').indexOf('ollama') &gt;= 0 ? ' fiscal-badge-engine--ai' : '');</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-ctx\">         var preview = document.getElementById('confirm-preview-image');</div><div class=\"diff-line diff-del\">-        if (item.image_url) {</div><div class=\"diff-line diff-del\">-            preview.innerHTML = '&lt;img src=\"' + item.image_url + '\" class=\"img-fluid rounded\" alt=\"Documento\"&gt;';</div><div class=\"diff-line diff-del\">-        } else {</div><div class=\"diff-line diff-del\">-            preview.innerHTML = '';</div><div class=\"diff-line diff-add\">+        if (preview) {</div><div class=\"diff-line diff-add\">+            if (item.image_url) {</div><div class=\"diff-line diff-add\">+                preview.innerHTML = '&lt;img src=\"' + item.image_url + '\" class=\"img-fluid rounded w-100\" alt=\"Documento escaneado\"&gt;';</div><div class=\"diff-line diff-add\">+            } else {</div><div class=\"diff-line diff-add\">+                preview.innerHTML = '&lt;div class=\"fiscal-preview-empty\"&gt;&lt;i class=\"fa-solid fa-file-invoice fa-2x mb-2\"&gt;&lt;/i&gt;&lt;div class=\"small\"&gt;Sin vista previa del documento&lt;/div&gt;&lt;/div&gt;';</div><div class=\"diff-line diff-add\">+            }</div><div class=\"diff-line diff-ctx\">         }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        var notes = document.getElementById('cf-notes');</div><div class=\"diff-line diff-add\">+        if (notes) {</div><div class=\"diff-line diff-add\">+            var noteText = item.notes || 'Revisa los datos antes de confirmar.';</div><div class=\"diff-line diff-add\">+            if (item.ocr_engine === 'manual') {</div><div class=\"diff-line diff-add\">+                noteText = 'Modo manual: verifica todos los campos obligatorios (RD 1619/2012).';</div><div class=\"diff-line diff-add\">+            }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+            notes.textContent = noteText;</div><div class=\"diff-line diff-add\">+            notes.className = 'alert alert-light border mt-3 small mb-0';</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+            var compliance = item.fiscal_compliance ?? item.confidence ?? 0;</div><div class=\"diff-line diff-add\">+            if ((item.validation_errors || []).length &gt; 0) {</div><div class=\"diff-line diff-add\">+                notes.className = 'alert alert-danger mt-3 small mb-0';</div><div class=\"diff-line diff-add\">+            } else if ((item.validation_warnings || item.warnings || []).length &gt; 0 || compliance &lt; 0.7) {</div><div class=\"diff-line diff-add\">+                notes.className = 'alert alert-warning mt-3 small mb-0';</div><div class=\"diff-line diff-add\">+            } else if (compliance &gt;= 0.85) {</div><div class=\"diff-line diff-add\">+                notes.className = 'alert alert-success mt-3 small mb-0';</div><div class=\"diff-line diff-add\">+            }</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        applyFieldStatus(item.field_status || {});</div><div class=\"diff-line diff-add\">+        renderFiscalAlerts(item);</div><div class=\"diff-line diff-add\">+        renderComplianceBar(item);</div><div class=\"diff-line diff-ctx\">     }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">     function blurActiveElement() {</div></div>","linesAdded":186,"linesRemoved":31,"status":"pending"},"pending":5},"at":1782350924186},{"event":"tool_activity","data":{"activity":{"id":"tool_101e3e5a-4caf-48b9-b86c-7182a2a6886","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:28:47.330Z"}},"at":1782350927330},{"event":"file_change","data":{"sessionId":"cs-1782350865700-aiddd4","change":{"id":"ch-6","path":"Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css\n@@ -238,17 +238,42 @@\n     color: #fff;\n }\n \n+.fiscal-badge-doc--issued {\n+    background: #6610f2;\n+}\n+\n .fiscal-badge-engine {\n     background: #e9ecef;\n     color: #495057;\n }\n \n+.fiscal-badge-engine--ai {\n+    background: rgba(102, 16, 242, 0.12);\n+    color: #6610f2;\n+}\n+\n .fiscal-preview img {\n-    max-height: 160px;\n+    max-height: 180px;\n+    object-fit: contain;\n     border-radius: 12px;\n     box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);\n+    background: #fff;\n }\n \n+.fiscal-preview-empty {\n+    display: flex;\n+    flex-direction: column;\n+    align-items: center;\n+    justify-content: center;\n+    min-height: 120px;\n+    border-radius: 12px;\n+    background: #fff;\n+    border: 1px dashed #ced4da;\n+    color: #6c757d;\n+    text-align: center;\n+    padding: 16px;\n+}\n+\n .fiscal-compliance-card {\n     background: #fff;\n     border-radius: 12px;","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css</div><div class=\"diff-line diff-hunk\">@@ -238,17 +238,42 @@</div><div class=\"diff-line diff-ctx\">     color: #fff;</div><div class=\"diff-line diff-ctx\"> }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-add\">+.fiscal-badge-doc--issued {</div><div class=\"diff-line diff-add\">+    background: #6610f2;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-ctx\"> .fiscal-badge-engine {</div><div class=\"diff-line diff-ctx\">     background: #e9ecef;</div><div class=\"diff-line diff-ctx\">     color: #495057;</div><div class=\"diff-line diff-ctx\"> }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-add\">+.fiscal-badge-engine--ai {</div><div class=\"diff-line diff-add\">+    background: rgba(102, 16, 242, 0.12);</div><div class=\"diff-line diff-add\">+    color: #6610f2;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-ctx\"> .fiscal-preview img {</div><div class=\"diff-line diff-del\">-    max-height: 160px;</div><div class=\"diff-line diff-add\">+    max-height: 180px;</div><div class=\"diff-line diff-add\">+    object-fit: contain;</div><div class=\"diff-line diff-ctx\">     border-radius: 12px;</div><div class=\"diff-line diff-ctx\">     box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);</div><div class=\"diff-line diff-add\">+    background: #fff;</div><div class=\"diff-line diff-ctx\"> }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-add\">+.fiscal-preview-empty {</div><div class=\"diff-line diff-add\">+    display: flex;</div><div class=\"diff-line diff-add\">+    flex-direction: column;</div><div class=\"diff-line diff-add\">+    align-items: center;</div><div class=\"diff-line diff-add\">+    justify-content: center;</div><div class=\"diff-line diff-add\">+    min-height: 120px;</div><div class=\"diff-line diff-add\">+    border-radius: 12px;</div><div class=\"diff-line diff-add\">+    background: #fff;</div><div class=\"diff-line diff-add\">+    border: 1px dashed #ced4da;</div><div class=\"diff-line diff-add\">+    color: #6c757d;</div><div class=\"diff-line diff-add\">+    text-align: center;</div><div class=\"diff-line diff-add\">+    padding: 16px;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-ctx\"> .fiscal-compliance-card {</div><div class=\"diff-line diff-ctx\">     background: #fff;</div><div class=\"diff-line diff-ctx\">     border-radius: 12px;</div></div>","linesAdded":26,"linesRemoved":1,"status":"pending"},"pending":6},"at":1782350927332},{"event":"tool_activity","data":{"activity":{"id":"tool_30c67ac7-cf93-4234-969c-28e42955fdb","name":"edit","status":"running","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:28:47.941Z"}},"at":1782350927941},{"event":"status","data":{"text":"Límite de 80 herramientas alcanzado — finalizando con lo obtenido."},"at":1782350927942},{"event":"status","data":{"text":"Turno añadido al transcript del IDE (reabrir chat para verlo)"},"at":1782350927946},{"event":"done","data":{"ok":true,"bridgeSessionId":"bs-1782350927945-i2w206","agentId":"agent-a46adb84-44ba-4df6-8c0b-cf9fe8cc0292","runId":"run-41d9c687-bf77-4896-bcaf-39016fc3301b","status":"cancelled","result":"","changeSessionId":"cs-1782350865700-aiddd4","pendingChanges":6,"changes":[{"id":"ch-1","callId":"tool_e8eb3fd4-eb0d-498f-9cbf-87100b3da25","path":"Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js\n@@ -4,7 +4,7 @@\n     var confirmModal = null;\n     var confirmModalEl = null;\n     var expenseMode = (window.ALIMPIEZAS_MOBILE && window.ALIMPIEZAS_MOBILE.expenseMode) || 'ocr';\n-    var JS_VERSION = '2026-06-25d';\n+    var JS_VERSION = '2026-06-25e';\n     var selectedFiles = [];\n     var fileIdCounter = 0;\n ","linesAdded":1,"linesRemoved":1,"status":"pending"},{"id":"ch-2","callId":"tool_e9373c60-9c57-422e-91ec-a181bab8421","path":"Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php\n@@ -125,7 +125,9 @@\n         $mode = MobileConfig::expenseMode();\n         $serviceUrl = MobileConfig::aiServiceUrl();\n         $service = MobileConfig::isAiServiceEnabled() ? self::checkServiceHealth() : ['ok' => true, 'skipped' => true];\n-        $ollama = $mode === MobileConfig::MODE_AI ? self::checkOllamaHealth() : ['ok' => null, 'skipped' => true];\n+        $ollama = MobileConfig::isAiServiceEnabled()\n+            ? self::checkOllamaHealth()\n+            : ['ok' => null, 'skipped' => true];\n \n         return [\n             'ok' => true,","linesAdded":3,"linesRemoved":1,"status":"pending"},{"id":"ch-3","callId":"tool_e5a8632d-b6e3-4999-a7f1-f48d3d543c3","path":"Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig\n@@ -14,7 +14,7 @@\n     <title>{{ fsc.title }}</title>\n     <link rel=\"stylesheet\" href=\"{{ asset('node_modules/bootstrap/dist/css/bootstrap.min.css') }}\">\n     <link rel=\"stylesheet\" href=\"{{ asset('node_modules/@fortawesome/fontawesome-free/css/all.min.css') }}\">\n-    <link rel=\"stylesheet\" href=\"{{ asset('Dinamic/Assets/CSS/mobile.css') }}?v=20260625d\">\n+    <link rel=\"stylesheet\" href=\"{{ asset('Dinamic/Assets/CSS/mobile.css') }}?v=20260625e\">\n     {% block head %}{% endblock %}\n </head>\n <body class=\"mobile-app\">\n@@ -67,11 +67,11 @@\n             expenseMode: {{ settings('alimpiezas', 'expense_mode', 'ocr')|json_encode|raw }}\n         };\n     </script>\n-    <script src=\"{{ asset('Dinamic/Assets/JS/help-guide.js') }}?v=20260625d\"></script>\n-    <script src=\"{{ asset('Dinamic/Assets/JS/mobile.js') }}?v=20260625d\"></script>\n+    <script src=\"{{ asset('Dinamic/Assets/JS/help-guide.js') }}?v=20260625e\"></script>\n+    <script src=\"{{ asset('Dinamic/Assets/JS/mobile.js') }}?v=20260625e\"></script>\n     <script>\n         if ('serviceWorker' in navigator) {\n-            navigator.serviceWorker.register('{{ asset('Dinamic/Assets/sw.js') }}?v=20260625d')\n+            navigator.serviceWorker.register('{{ asset('Dinamic/Assets/sw.js') }}?v=20260625e')\n                 .then(function (reg) {\n                     if (reg.waiting) {\n                         reg.waiting.postMessage({ type: 'SKIP_WAITING' });","linesAdded":4,"linesRemoved":4,"status":"pending"},{"id":"ch-4","callId":"tool_f5664b19-1aca-48bd-9774-f75321d4dae","path":"Plugins\\ALimpiezasMobile\\Assets\\sw.js","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js\n@@ -1,4 +1,4 @@\n-const CACHE_NAME = 'alimpiezas-mobile-v4';\n+const CACHE_NAME = 'alimpiezas-mobile-v5';\n const CORE_ASSETS = [\n     '/ALimpiezas/Dinamic/Assets/CSS/mobile.css',\n     '/ALimpiezas/Dinamic/Assets/JS/mobile.js',","linesAdded":1,"linesRemoved":1,"status":"pending"},{"id":"ch-5","callId":"tool_61915eb0-7c78-4dc5-affa-bfa2522f30a","path":"Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js\n@@ -359,49 +359,204 @@\n         }\n     }\n \n-    function fillConfirmForm(item) {\n-        document.getElementById('confirm-queue-id').value = item.id;\n-        document.getElementById('cf-supplier-name').value = item.supplier_name || '';\n-        document.getElementById('cf-supplier-cif').value = item.supplier_cif || '';\n-        document.getElementById('cf-invoice-number').value = item.invoice_number || '';\n-        document.getElementById('cf-invoice-date').value = item.invoice_date || '';\n-        document.getElementById('cf-base').value = item.base_amount || '';\n-        document.getElementById('cf-tax').value = item.tax_amount || '';\n-        document.getElementById('cf-total').value = item.total_amount || '';\n+    function setConfirmField(id, value) {\n+        var el = document.getElementById(id);\n+        if (!el) {\n+            return;\n+        }\n+        if (value === null || value === undefined) {\n+            el.value = '';\n+            return;\n+        }\n+        el.value = value;\n+    }\n \n-        var notes = document.getElementById('cf-notes');\n-        var noteText = '';\n-        var alertClass = 'alert-info';\n+    function setSelectValue(id, value) {\n+        var el = document.getElementById(id);\n+        if (!el || value === null || value === undefined || value === '') {\n+            return;\n+        }\n+        var normalized = String(Math.round(parseFloat(value)));\n+        if (el.querySelector('option[value=\"' + normalized + '\"]')) {\n+            el.value = normalized;\n+        }\n+    }\n \n-        if (item.ocr_engine === 'manual') {\n-            noteText = 'Modo manual: verifica todos los campos antes de confirmar.';\n-        } else {\n-            noteText = 'Confianza: ' + Math.round((item.confidence || 0) * 100) + '%';\n-            if (item.ocr_engine) {\n-                noteText += ' · Motor: ' + item.ocr_engine;\n+    function documentKindLabel(kind, docType) {\n+        var labels = {\n+            expense_received: 'Factura recibida / gasto',\n+            issued_invoice: 'Factura emitida',\n+            simplified_ticket: 'Ticket simplificado',\n+            proforma: 'Proforma',\n+            unknown: 'Documento contable'\n+        };\n+        if (labels[kind]) {\n+            return labels[kind];\n+        }\n+        return docType === 'issued' ? 'Factura emitida' : 'Factura recibida / gasto';\n+    }\n+\n+    function engineLabel(engine) {\n+        if (!engine || engine === 'manual') {\n+            return 'Entrada manual';\n+        }\n+        return String(engine).replace(/\\+/g, ' + ').replace(/ollama/i, 'Ollama');\n+    }\n+\n+    function applyFieldStatus(fieldStatus) {\n+        document.querySelectorAll('.fiscal-field').forEach(function (field) {\n+            field.classList.remove('fiscal-field--ok', 'fiscal-field--review', 'fiscal-field--missing');\n+            var key = field.getAttribute('data-field');\n+            var status = fieldStatus && fieldStatus[key];\n+            if (status === 'ok') {\n+                field.classList.add('fiscal-field--ok');\n+            } else if (status === 'review') {\n+                field.classList.add('fiscal-field--review');\n+            } else if (status === 'missing') {\n+                field.classList.add('fiscal-field--missing');\n             }\n-            if ((item.confidence || 0) < 0.4) {\n-                alertClass = 'alert-warning';\n+        });\n+    }\n+\n+    function renderFiscalAlerts(item) {\n+        var container = document.getElementById('cf-alerts');\n+        if (!container) {\n+            return;\n+        }\n+\n+        container.innerHTML = '';\n+        var errors = item.validation_errors || [];\n+        var warnings = item.validation_warnings || item.warnings || [];\n+\n+        errors.forEach(function (msg) {\n+            var row = document.createElement('div');\n+            row.className = 'fiscal-alert-item fiscal-alert-error';\n+            row.innerHTML = '<i class=\"fa-solid fa-circle-xmark me-2\"></i>' + msg;\n+            container.appendChild(row);\n+        });\n+\n+        warnings.forEach(function (msg) {\n+            var row = document.createElement('div');\n+            row.className = 'fiscal-alert-item fiscal-alert-warning';\n+            row.innerHTML = '<i class=\"fa-solid fa-triangle-exclamation me-2\"></i>' + msg;\n+            container.appendChild(row);\n+        });\n+    }\n+\n+    function renderComplianceBar(item) {\n+        var compliance = item.fiscal_compliance;\n+        if (compliance === null || compliance === undefined) {\n+            compliance = item.confidence || 0;\n+        }\n+\n+        var pct = Math.round(Number(compliance) * 100);\n+        var fill = document.getElementById('cf-compliance-fill');\n+        var pctEl = document.getElementById('cf-compliance-pct');\n+        var confText = document.getElementById('cf-confidence-text');\n+        var wrap = document.getElementById('cf-compliance-wrap');\n+\n+        if (fill) {\n+            fill.style.width = Math.max(0, Math.min(100, pct)) + '%';\n+            fill.classList.remove('compliance-high', 'compliance-mid', 'compliance-low');\n+            if (pct >= 80) {\n+                fill.classList.add('compliance-high');\n+            } else if (pct >= 50) {\n+                fill.classList.add('compliance-mid');\n+            } else {\n+                fill.classList.add('compliance-low');\n             }\n         }\n \n-        if (item.warnings && item.warnings.length) {\n-            alertClass = 'alert-warning';\n-            noteText += '\\n' + item.warnings.join(' · ');\n+        if (pctEl) {\n+            pctEl.textContent = pct + '%';\n+            pctEl.className = 'small fw-semibold ' + (pct >= 80 ? 'text-success' : pct >= 50 ? 'text-warning' : 'text-danger');\n         }\n-        if (item.notes) {\n-            noteText += '\\n' + item.notes;\n+\n+        if (confText) {\n+            confText.textContent = 'Motor: ' + engineLabel(item.ocr_engine)\n+                + ' · Confianza extracción: ' + Math.round((item.confidence || 0) * 100) + '%';\n         }\n \n-        notes.className = 'alert mt-3 small mb-0 ' + alertClass;\n-        notes.textContent = noteText;\n+        if (wrap) {\n+            wrap.classList.toggle('compliance-ready', pct >= 80);\n+            wrap.classList.toggle('compliance-review', pct >= 50 && pct < 80);\n+            wrap.classList.toggle('compliance-risk', pct < 50);\n+        }\n+    }\n \n+    function fillConfirmForm(item) {\n+        setConfirmField('confirm-queue-id', item.id);\n+        setConfirmField('cf-doc-type', item.doc_type || 'expense');\n+        setConfirmField('cf-supplier-name', item.supplier_name);\n+        setConfirmField('cf-supplier-cif', item.supplier_cif);\n+        setConfirmField('cf-address', item.counterparty_address);\n+        setConfirmField('cf-invoice-series', item.invoice_series);\n+        setConfirmField('cf-invoice-number', item.invoice_number);\n+        setConfirmField('cf-invoice-date', item.invoice_date);\n+        setConfirmField('cf-operation-date', item.operation_date || item.invoice_date);\n+        setConfirmField('cf-concept', item.concept);\n+        setConfirmField('cf-base', item.base_amount);\n+        setConfirmField('cf-tax', item.tax_amount);\n+        setConfirmField('cf-total', item.total_amount);\n+        setConfirmField('cf-irpf-amount', item.irpf_amount);\n+        setSelectValue('cf-tax-rate', item.tax_rate);\n+        setSelectValue('cf-irpf-rate', item.irpf_rate);\n+\n+        var deductible = document.getElementById('cf-deductible');\n+        if (deductible) {\n+            deductible.checked = item.deductible !== false && item.doc_type !== 'issued';\n+        }\n+\n+        var rectifying = document.getElementById('cf-rectifying');\n+        if (rectifying) {\n+            rectifying.checked = !!item.is_rectifying;\n+        }\n+\n+        var docBadge = document.getElementById('cf-doc-kind-badge');\n+        if (docBadge) {\n+            docBadge.textContent = documentKindLabel(item.document_kind, item.doc_type);\n+            docBadge.className = 'badge fiscal-badge-doc' + (item.doc_type === 'issued' ? ' fiscal-badge-doc--issued' : '');\n+        }\n+\n+        var engineBadge = document.getElementById('cf-engine-badge');\n+        if (engineBadge) {\n+            engineBadge.textContent = engineLabel(item.ocr_engine);\n+            engineBadge.className = 'badge fiscal-badge-engine'\n+                + (String(item.ocr_engine || '').indexOf('ollama') >= 0 ? ' fiscal-badge-engine--ai' : '');\n+        }\n+\n         var preview = document.getElementById('confirm-preview-image');\n-        if (item.image_url) {\n-            preview.innerHTML = '<img src=\"' + item.image_url + '\" class=\"img-fluid rounded\" alt=\"Documento\">';\n-        } else {\n-            preview.innerHTML = '';\n+        if (preview) {\n+            if (item.image_url) {\n+                preview.innerHTML = '<img src=\"' + item.image_url + '\" class=\"img-fluid rounded w-100\" alt=\"Documento escaneado\">';\n+            } else {\n+                preview.innerHTML = '<div class=\"fiscal-preview-empty\"><i class=\"fa-solid fa-file-invoice fa-2x mb-2\"></i><div class=\"small\">Sin vista previa del documento</div></div>';\n+            }\n         }\n+\n+        var notes = document.getElementById('cf-notes');\n+        if (notes) {\n+            var noteText = item.notes || 'Revisa los datos antes de confirmar.';\n+            if (item.ocr_engine === 'manual') {\n+                noteText = 'Modo manual: verifica todos los campos obligatorios (RD 1619/2012).';\n+            }\n+\n+            notes.textContent = noteText;\n+            notes.className = 'alert alert-light border mt-3 small mb-0';\n+\n+            var compliance = item.fiscal_compliance ?? item.confidence ?? 0;\n+            if ((item.validation_errors || []).length > 0) {\n+                notes.className = 'alert alert-danger mt-3 small mb-0';\n+            } else if ((item.validation_warnings || item.warnings || []).length > 0 || compliance < 0.7) {\n+                notes.className = 'alert alert-warning mt-3 small mb-0';\n+            } else if (compliance >= 0.85) {\n+                notes.className = 'alert alert-success mt-3 small mb-0';\n+            }\n+        }\n+\n+        applyFieldStatus(item.field_status || {});\n+        renderFiscalAlerts(item);\n+        renderComplianceBar(item);\n     }\n \n     function blurActiveElement() {","linesAdded":186,"linesRemoved":31,"status":"pending"},{"id":"ch-6","callId":"tool_101e3e5a-4caf-48b9-b86c-7182a2a6886","path":"Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css\n@@ -238,17 +238,42 @@\n     color: #fff;\n }\n \n+.fiscal-badge-doc--issued {\n+    background: #6610f2;\n+}\n+\n .fiscal-badge-engine {\n     background: #e9ecef;\n     color: #495057;\n }\n \n+.fiscal-badge-engine--ai {\n+    background: rgba(102, 16, 242, 0.12);\n+    color: #6610f2;\n+}\n+\n .fiscal-preview img {\n-    max-height: 160px;\n+    max-height: 180px;\n+    object-fit: contain;\n     border-radius: 12px;\n     box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);\n+    background: #fff;\n }\n \n+.fiscal-preview-empty {\n+    display: flex;\n+    flex-direction: column;\n+    align-items: center;\n+    justify-content: center;\n+    min-height: 120px;\n+    border-radius: 12px;\n+    background: #fff;\n+    border: 1px dashed #ced4da;\n+    color: #6c757d;\n+    text-align: center;\n+    padding: 16px;\n+}\n+\n .fiscal-compliance-card {\n     background: #fff;\n     border-radius: 12px;","linesAdded":26,"linesRemoved":1,"status":"pending"}],"transcriptPath":"C:\\Users\\Paterna\\.cursor\\projects\\c-xampp-htdocs-ALimpiezas\\agent-transcripts\\736ac9a4-90f0-4491-8940-56ec149befda\\736ac9a4-90f0-4491-8940-56ec149befda.jsonl","notifyTitle":"Agente terminado","notifyBody":"Continúo donde se cortó: reviso el estado de Twig, CSS y JS de confirmación fiscal y completo lo pendiente.\nCompletando "},"at":1782350927947}]}
