{"id":"cs-1782349514618-4a2htjnr","status":"done","createdAt":1782349514618,"finishedAt":1782349581506,"meta":{"projectName":"ALimpiezas","projectPath":"C:\\xampp\\htdocs\\ALimpiezas","messagePreview":"Da error: Gastos e IA\nModo activo\nOCR (Tesseract)\nRegistrar gasto\n\nTipo \nDocumento (foto, galería o PDF — obligatorio) \n","phaseId":null},"events":[{"event":"status","data":{"text":"Modo IDE / sesión — completo (80 herramientas, uso selectivo)"},"at":1782349514621},{"event":"status","data":{"text":"Agente"},"at":1782349514625},{"event":"status","data":{"text":"Modo revisión: snapshot local por archivo"},"at":1782349514626},{"event":"status","data":{"text":"Contexto del IDE cargado"},"at":1782349514626},{"event":"agent","data":{"agentId":"agent-db24b4ef-2ed7-4ef1-a64b-a3d4d84f72fa","projectPath":"C:\\xampp\\htdocs\\ALimpiezas"},"at":1782349514626},{"event":"changes","data":{"sessionId":"cs-1782349514621-fwmksd","pending":0},"at":1782349514626},{"event":"status","data":{"text":"Procesando…"},"at":1782349514691},{"event":"tool_activity","data":{"activity":{"id":"tool_25890fe5-6c90-49b6-864b-043213905e3","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:05:17.322Z"}},"at":1782349517322},{"event":"tool_activity","data":{"activity":{"id":"tool_d14b9ce7-ebef-4500-9ccf-e60b3620fd6","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:05:17.325Z"}},"at":1782349517325},{"event":"text","data":{"chunk":"Continúo el hilo: reviso los archivos del OCR móvil ya identificados y el error al registrar gastos.\n"},"at":1782349581506},{"event":"tool_activity","data":{"activity":{"id":"tool_d14b9ce7-ebef-4500-9ccf-e60b3620fd6","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:05:17.526Z"}},"at":1782349517526},{"event":"tool_activity","data":{"activity":{"id":"tool_25890fe5-6c90-49b6-864b-043213905e3","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:05:17.562Z"}},"at":1782349517562},{"event":"tool_activity","data":{"activity":{"id":"tool_886d2db1-683a-4d28-809f-e4407e9f833","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:05:18.300Z"}},"at":1782349518300},{"event":"tool_activity","data":{"activity":{"id":"tool_422fe793-3c1c-4da2-a569-62093c42983","name":"read","status":"completed","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Controller\\MobileExpenses.php","detail":"","at":"2026-06-25T01:05:18.412Z"}},"at":1782349518412},{"event":"tool_activity","data":{"activity":{"id":"tool_886d2db1-683a-4d28-809f-e4407e9f833","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:05:18.416Z"}},"at":1782349518416},{"event":"tool_activity","data":{"activity":{"id":"tool_55443cfd-4aaa-489c-a804-204f2d4b844","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:05:18.615Z"}},"at":1782349518615},{"event":"tool_activity","data":{"activity":{"id":"tool_aaee90e7-f6a2-433e-8f88-076f26744c1","name":"read","status":"completed","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:05:18.762Z"}},"at":1782349518762},{"event":"tool_activity","data":{"activity":{"id":"tool_1dcbe1d8-9c90-412e-8b26-79c3555ccd2","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\MobileConfig.php","detail":"","at":"2026-06-25T01:05:21.554Z"}},"at":1782349521554},{"event":"tool_activity","data":{"activity":{"id":"tool_e6635d86-5c03-4ef2-b7af-d15ff47d6f7","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Init.php","detail":"","at":"2026-06-25T01:05:21.555Z"}},"at":1782349521555},{"event":"tool_activity","data":{"activity":{"id":"tool_1dcbe1d8-9c90-412e-8b26-79c3555ccd2","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\MobileConfig.php","detail":"","at":"2026-06-25T01:05:21.648Z"}},"at":1782349521648},{"event":"tool_activity","data":{"activity":{"id":"tool_e6635d86-5c03-4ef2-b7af-d15ff47d6f7","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Init.php","detail":"","at":"2026-06-25T01:05:21.651Z"}},"at":1782349521651},{"event":"tool_activity","data":{"activity":{"id":"tool_facbf7a8-dd07-44f7-8342-2c2ab449314","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:05:21.676Z"}},"at":1782349521676},{"event":"tool_activity","data":{"activity":{"id":"tool_f915f594-08d7-4a7c-994e-e88721e2396","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:05:21.753Z"}},"at":1782349521753},{"event":"tool_activity","data":{"activity":{"id":"tool_facbf7a8-dd07-44f7-8342-2c2ab449314","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:05:21.770Z"}},"at":1782349521770},{"event":"tool_activity","data":{"activity":{"id":"tool_f915f594-08d7-4a7c-994e-e88721e2396","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:05:21.935Z"}},"at":1782349521935},{"event":"tool_activity","data":{"activity":{"id":"tool_b4d36d2f-f215-4801-a5fa-bfcbf9be08e","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py","detail":"","at":"2026-06-25T01:05:23.708Z"}},"at":1782349523708},{"event":"tool_activity","data":{"activity":{"id":"tool_170242b3-6f8f-4b05-a2a8-52cf5300a1d","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:05:23.708Z"}},"at":1782349523708},{"event":"tool_activity","data":{"activity":{"id":"tool_03164420-afd6-42b7-aa51-e2d91eb8908","name":"read","status":"running","summary":"read: …htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig","detail":"","at":"2026-06-25T01:05:23.709Z"}},"at":1782349523709},{"event":"tool_activity","data":{"activity":{"id":"tool_d93d1123-3b0f-49e7-849b-09b75271f46","name":"shell","status":"running","summary":"shell","detail":"curl -s -m 5 \"http://127.0.0.1:8090/health\" 2>&1; echo \"---\"; curl -s -m 5 \"http://127.0.0.1:8090/api/v1/extract-invoice\" -X POST 2>&1 | head -c 500","at":"2026-06-25T01:05:23.710Z"}},"at":1782349523710},{"event":"tool_activity","data":{"activity":{"id":"tool_b4d36d2f-f215-4801-a5fa-bfcbf9be08e","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py","detail":"","at":"2026-06-25T01:05:23.815Z"}},"at":1782349523815},{"event":"tool_activity","data":{"activity":{"id":"tool_03164420-afd6-42b7-aa51-e2d91eb8908","name":"read","status":"completed","summary":"read: …htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\Master\\MobileLayout.html.twig","detail":"","at":"2026-06-25T01:05:23.816Z"}},"at":1782349523816},{"event":"tool_activity","data":{"activity":{"id":"tool_170242b3-6f8f-4b05-a2a8-52cf5300a1d","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:05:23.818Z"}},"at":1782349523818},{"event":"tool_activity","data":{"activity":{"id":"tool_d93d1123-3b0f-49e7-849b-09b75271f46","name":"shell","status":"completed","summary":"shell","detail":"curl -s -m 5 \"http://127.0.0.1:8090/health\" 2>&1; echo \"---\"; curl -s -m 5 \"http://127.0.0.1:8090/api/v1/extract-invoice\" -X POST 2>&1 | head -c 500","at":"2026-06-25T01:05:24.853Z"}},"at":1782349524853},{"event":"tool_activity","data":{"activity":{"id":"tool_71772d49-49d9-42fe-9b9a-d9ddcc6b760","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:05:31.777Z"}},"at":1782349531777},{"event":"tool_activity","data":{"activity":{"id":"tool_2b611f26-884c-4d89-bce2-4aafd679927","name":"read","status":"running","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:05:31.778Z"}},"at":1782349531778},{"event":"tool_activity","data":{"activity":{"id":"tool_230e79fa-9e3e-4be3-a8ad-be2ef8ddc28","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:05:31.779Z"}},"at":1782349531779},{"event":"tool_activity","data":{"activity":{"id":"tool_2b611f26-884c-4d89-bce2-4aafd679927","name":"read","status":"completed","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:05:31.890Z"}},"at":1782349531891},{"event":"tool_activity","data":{"activity":{"id":"tool_71772d49-49d9-42fe-9b9a-d9ddcc6b760","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:05:31.928Z"}},"at":1782349531928},{"event":"tool_activity","data":{"activity":{"id":"tool_230e79fa-9e3e-4be3-a8ad-be2ef8ddc28","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:05:31.936Z"}},"at":1782349531936},{"event":"text","data":{"chunk":"El servicio OCR responde en el puerto 8090; reviso el selector de archivos en móvil y pruebo la subida end-to-end.\n"},"at":1782349581506},{"event":"tool_activity","data":{"activity":{"id":"tool_fb69f98d-7bcc-4d89-8a16-b8133d50f47","name":"shell","status":"completed","summary":"shell","detail":"$testImg = \"$env:TEMP\\ocr-test.png\"; Add-Type -AssemblyName System.Drawing; $bmp = New-Object System.Drawing.Bitmap 400,120; $g = [System.Drawing.Graphics]::FromImage($bmp); $g.Clear([System.Drawing.…","at":"2026-06-25T01:05:34.424Z"}},"at":1782349534424},{"event":"tool_activity","data":{"activity":{"id":"tool_30438ba3-c4f7-4318-84f5-7297cf5865c","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\mobile.html","detail":"","at":"2026-06-25T01:05:39.809Z"}},"at":1782349539809},{"event":"tool_activity","data":{"activity":{"id":"tool_8c9c8540-8463-4e40-87bf-1f2d2db604d","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:05:39.810Z"}},"at":1782349539810},{"event":"tool_activity","data":{"activity":{"id":"tool_390d0e61-2e0f-41ee-8fc3-e472dcc7e36","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:05:39.812Z"}},"at":1782349539812},{"event":"tool_activity","data":{"activity":{"id":"tool_30438ba3-c4f7-4318-84f5-7297cf5865c","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\mobile.html","detail":"","at":"2026-06-25T01:05:39.922Z"}},"at":1782349539922},{"event":"tool_activity","data":{"activity":{"id":"tool_8c9c8540-8463-4e40-87bf-1f2d2db604d","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:05:39.961Z"}},"at":1782349539961},{"event":"tool_activity","data":{"activity":{"id":"tool_390d0e61-2e0f-41ee-8fc3-e472dcc7e36","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:05:40.009Z"}},"at":1782349540009},{"event":"tool_activity","data":{"activity":{"id":"tool_fb55bd25-6988-4264-b695-515a119061e","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:05:42.724Z"}},"at":1782349542724},{"event":"tool_activity","data":{"activity":{"id":"tool_2b7308d4-608e-42dd-8056-25fcbce1c5e","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic","detail":"","at":"2026-06-25T01:05:42.725Z"}},"at":1782349542725},{"event":"tool_activity","data":{"activity":{"id":"tool_fb55bd25-6988-4264-b695-515a119061e","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:05:42.987Z"}},"at":1782349542987},{"event":"tool_activity","data":{"activity":{"id":"tool_2b7308d4-608e-42dd-8056-25fcbce1c5e","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic","detail":"","at":"2026-06-25T01:05:43.006Z"}},"at":1782349543006},{"event":"text","data":{"chunk":"El runtime usa `Dinamic`, no `Plugins`; reviso qué vers"},"at":1782349581506},{"event":"tool_activity","data":{"activity":{"id":"tool_05e362b3-8226-4b34-b05b-2626177dd8e","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:05:44.320Z"}},"at":1782349544320},{"event":"tool_activity","data":{"activity":{"id":"tool_4f4de87d-0f84-4612-93b8-770c16b57c2","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:05:44.321Z"}},"at":1782349544321},{"event":"tool_activity","data":{"activity":{"id":"tool_48363bc4-b88e-4f39-ade0-6dec7d83621","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Controller\\MobileExpenses.php","detail":"","at":"2026-06-25T01:05:44.365Z"}},"at":1782349544365},{"event":"tool_activity","data":{"activity":{"id":"tool_05e362b3-8226-4b34-b05b-2626177dd8e","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:05:44.422Z"}},"at":1782349544422},{"event":"tool_activity","data":{"activity":{"id":"tool_4f4de87d-0f84-4612-93b8-770c16b57c2","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:05:44.426Z"}},"at":1782349544426},{"event":"tool_activity","data":{"activity":{"id":"tool_48363bc4-b88e-4f39-ade0-6dec7d83621","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Controller\\MobileExpenses.php","detail":"","at":"2026-06-25T01:05:44.500Z"}},"at":1782349544500},{"event":"text","data":{"chunk":"ión se está sirviendo realmente.\n"},"at":1782349581506},{"event":"tool_activity","data":{"activity":{"id":"tool_29749830-c5ca-4a9d-83f3-f309f6b7909","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic","detail":"","at":"2026-06-25T01:05:46.673Z"}},"at":1782349546673},{"event":"text","data":{"chunk":"Encontré el bug: en `Dinamic/Assets/JS/mobile.js` se usa `fileInput` sin definir, lo que rompe el envío en modo OCR.\n"},"at":1782349581506},{"event":"tool_activity","data":{"activity":{"id":"tool_b77a2d00-ba2f-49a5-870e-9ce4ed8893b","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:05:46.809Z"}},"at":1782349546809},{"event":"tool_activity","data":{"activity":{"id":"tool_29749830-c5ca-4a9d-83f3-f309f6b7909","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic","detail":"","at":"2026-06-25T01:05:46.837Z"}},"at":1782349546837},{"event":"tool_activity","data":{"activity":{"id":"tool_b77a2d00-ba2f-49a5-870e-9ce4ed8893b","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:05:46.955Z"}},"at":1782349546955},{"event":"tool_activity","data":{"activity":{"id":"tool_42e82e1e-114d-4296-96fb-3addb144fb8","name":"edit","status":"running","summary":"edit: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:05:50.702Z"}},"at":1782349550702},{"event":"text","data":{"chunk":"Corrigiendo el bug de `fileInput` sin definir y sincronizando Plugins con Dinamic (runtime).\n"},"at":1782349581506},{"event":"tool_activity","data":{"activity":{"id":"tool_42e82e1e-114d-4296-96fb-3addb144fb8","name":"edit","status":"running","summary":"edit: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:05:56.110Z"}},"at":1782349556110},{"event":"tool_activity","data":{"activity":{"id":"tool_fe6df60a-61b6-4d63-83c2-f5dd044f6d3","name":"edit","status":"running","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:05:56.110Z"}},"at":1782349556110},{"event":"tool_activity","data":{"activity":{"id":"tool_42e82e1e-114d-4296-96fb-3addb144fb8","name":"edit","status":"completed","summary":"edit: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:05:56.325Z"}},"at":1782349556325},{"event":"file_change","data":{"sessionId":"cs-1782349514621-fwmksd","change":{"id":"ch-1","path":"Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig\n@@ -32,22 +32,22 @@\n                     <label class=\"form-label\">Documento <span class=\"text-muted\" id=\"file-label-hint\">(obligatorio para OCR)</span></label>\n                     <input type=\"file\" name=\"image\" id=\"file-input-active\" class=\"visually-hidden\" tabindex=\"-1\" aria-hidden=\"true\" accept=\"image/*,.pdf,application/pdf,.heic,.heif\">\n                     <div class=\"d-grid gap-2 mb-2\">\n-                        <label class=\"file-picker-wrap mb-0\" id=\"pick-file-wrap\">\n-                            <input type=\"file\" id=\"file-input-gallery\" class=\"visually-hidden\"\n-                                   accept=\"image/*,.pdf,application/pdf,.heic,.heif\"\n-                                   aria-label=\"Elegir archivo o galería\">\n+                        <div class=\"file-picker-wrap\" id=\"pick-file-wrap\">\n                             <span class=\"btn btn-outline-secondary btn-lg w-100\">\n                                 <i class=\"fa-solid fa-folder-open me-2\"></i>Elegir archivo o galería\n                             </span>\n-                        </label>\n-                        <label class=\"file-picker-wrap mb-0\" id=\"pick-camera-wrap\">\n-                            <input type=\"file\" id=\"file-input-camera\" class=\"visually-hidden\"\n-                                   accept=\"image/*\" capture=\"environment\"\n-                                   aria-label=\"Hacer foto con cámara\">\n+                            <input type=\"file\" id=\"file-input-gallery\" class=\"file-picker-overlay\"\n+                                   accept=\"image/*,.pdf,application/pdf,.heic,.heif\"\n+                                   aria-label=\"Elegir archivo o galería\">\n+                        </div>\n+                        <div class=\"file-picker-wrap\" id=\"pick-camera-wrap\">\n                             <span class=\"btn btn-outline-secondary btn-lg w-100\">\n                                 <i class=\"fa-solid fa-camera me-2\"></i>Hacer foto\n                             </span>\n-                        </label>\n+                            <input type=\"file\" id=\"file-input-camera\" class=\"file-picker-overlay\"\n+                                   accept=\"image/*\" capture=\"environment\"\n+                                   aria-label=\"Hacer foto con cámara\">\n+                        </div>\n                     </div>\n                     <div id=\"file-selected-name\" class=\"small text-muted\">Ningún documento seleccionado</div>\n                 </div>\n@@ -61,7 +61,7 @@\n                 </div>\n             </form>\n             <div id=\"upload-result\" class=\"mt-3\"></div>\n-            <details class=\"mt-3 debug-panel-wrap\">\n+            <details class=\"mt-3 debug-panel-wrap\" id=\"debug-panel-wrap\">\n                 <summary class=\"small text-muted\">Debug conexión OCR/IA</summary>\n                 <div id=\"debug-panel\" class=\"debug-panel small mt-2\"></div>\n             </details>","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig</div><div class=\"diff-line diff-hunk\">@@ -32,22 +32,22 @@</div><div class=\"diff-line diff-ctx\">                     &lt;label class=\"form-label\"&gt;Documento &lt;span class=\"text-muted\" id=\"file-label-hint\"&gt;(obligatorio para OCR)&lt;/span&gt;&lt;/label&gt;</div><div class=\"diff-line diff-ctx\">                     &lt;input type=\"file\" name=\"image\" id=\"file-input-active\" class=\"visually-hidden\" tabindex=\"-1\" aria-hidden=\"true\" accept=\"image/*,.pdf,application/pdf,.heic,.heif\"&gt;</div><div class=\"diff-line diff-ctx\">                     &lt;div class=\"d-grid gap-2 mb-2\"&gt;</div><div class=\"diff-line diff-del\">-                        &lt;label class=\"file-picker-wrap mb-0\" id=\"pick-file-wrap\"&gt;</div><div class=\"diff-line diff-del\">-                            &lt;input type=\"file\" id=\"file-input-gallery\" class=\"visually-hidden\"</div><div class=\"diff-line diff-del\">-                                   accept=\"image/*,.pdf,application/pdf,.heic,.heif\"</div><div class=\"diff-line diff-del\">-                                   aria-label=\"Elegir archivo o galería\"&gt;</div><div class=\"diff-line diff-add\">+                        &lt;div class=\"file-picker-wrap\" id=\"pick-file-wrap\"&gt;</div><div class=\"diff-line diff-ctx\">                             &lt;span class=\"btn btn-outline-secondary btn-lg w-100\"&gt;</div><div class=\"diff-line diff-ctx\">                                 &lt;i class=\"fa-solid fa-folder-open me-2\"&gt;&lt;/i&gt;Elegir archivo o galería</div><div class=\"diff-line diff-ctx\">                             &lt;/span&gt;</div><div class=\"diff-line diff-del\">-                        &lt;/label&gt;</div><div class=\"diff-line diff-del\">-                        &lt;label class=\"file-picker-wrap mb-0\" id=\"pick-camera-wrap\"&gt;</div><div class=\"diff-line diff-del\">-                            &lt;input type=\"file\" id=\"file-input-camera\" class=\"visually-hidden\"</div><div class=\"diff-line diff-del\">-                                   accept=\"image/*\" capture=\"environment\"</div><div class=\"diff-line diff-del\">-                                   aria-label=\"Hacer foto con cámara\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;input type=\"file\" id=\"file-input-gallery\" class=\"file-picker-overlay\"</div><div class=\"diff-line diff-add\">+                                   accept=\"image/*,.pdf,application/pdf,.heic,.heif\"</div><div class=\"diff-line diff-add\">+                                   aria-label=\"Elegir archivo o galería\"&gt;</div><div class=\"diff-line diff-add\">+                        &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                        &lt;div class=\"file-picker-wrap\" id=\"pick-camera-wrap\"&gt;</div><div class=\"diff-line diff-ctx\">                             &lt;span class=\"btn btn-outline-secondary btn-lg w-100\"&gt;</div><div class=\"diff-line diff-ctx\">                                 &lt;i class=\"fa-solid fa-camera me-2\"&gt;&lt;/i&gt;Hacer foto</div><div class=\"diff-line diff-ctx\">                             &lt;/span&gt;</div><div class=\"diff-line diff-del\">-                        &lt;/label&gt;</div><div class=\"diff-line diff-add\">+                            &lt;input type=\"file\" id=\"file-input-camera\" class=\"file-picker-overlay\"</div><div class=\"diff-line diff-add\">+                                   accept=\"image/*\" capture=\"environment\"</div><div class=\"diff-line diff-add\">+                                   aria-label=\"Hacer foto con cámara\"&gt;</div><div class=\"diff-line diff-add\">+                        &lt;/div&gt;</div><div class=\"diff-line diff-ctx\">                     &lt;/div&gt;</div><div class=\"diff-line diff-ctx\">                     &lt;div id=\"file-selected-name\" class=\"small text-muted\"&gt;Ningún documento seleccionado&lt;/div&gt;</div><div class=\"diff-line diff-ctx\">                 &lt;/div&gt;</div><div class=\"diff-line diff-hunk\">@@ -61,7 +61,7 @@</div><div class=\"diff-line diff-ctx\">                 &lt;/div&gt;</div><div class=\"diff-line diff-ctx\">             &lt;/form&gt;</div><div class=\"diff-line diff-ctx\">             &lt;div id=\"upload-result\" class=\"mt-3\"&gt;&lt;/div&gt;</div><div class=\"diff-line diff-del\">-            &lt;details class=\"mt-3 debug-panel-wrap\"&gt;</div><div class=\"diff-line diff-add\">+            &lt;details class=\"mt-3 debug-panel-wrap\" id=\"debug-panel-wrap\"&gt;</div><div class=\"diff-line diff-ctx\">                 &lt;summary class=\"small text-muted\"&gt;Debug conexión OCR/IA&lt;/summary&gt;</div><div class=\"diff-line diff-ctx\">                 &lt;div id=\"debug-panel\" class=\"debug-panel small mt-2\"&gt;&lt;/div&gt;</div><div class=\"diff-line diff-ctx\">             &lt;/details&gt;</div></div>","linesAdded":11,"linesRemoved":11,"status":"pending"},"pending":1},"at":1782349556326},{"event":"tool_activity","data":{"activity":{"id":"tool_fe6df60a-61b6-4d63-83c2-f5dd044f6d3","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:06:09.015Z"}},"at":1782349569015},{"event":"file_change","data":{"sessionId":"cs-1782349514621-fwmksd","change":{"id":"ch-2","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,6 +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-25b';\n \n     function route(path) {\n         var base = (window.ALIMPIEZAS_MOBILE && window.ALIMPIEZAS_MOBILE.route) || '';\n@@ -19,6 +20,19 @@\n         return tokenInput ? tokenInput.value : '';\n     }\n \n+    function parseJsonResponse(response) {\n+        return response.text().then(function (text) {\n+            if (!text) {\n+                throw new Error('Respuesta vacía del servidor (HTTP ' + response.status + ')');\n+            }\n+            try {\n+                return JSON.parse(text);\n+            } catch (err) {\n+                throw new Error('Respuesta no JSON: ' + text.substring(0, 180));\n+            }\n+        });\n+    }\n+\n     function postForm(data) {\n         if (!data.has('multireqtoken')) {\n             data.append('multireqtoken', tokenValue());\n@@ -27,7 +41,7 @@\n             method: 'POST',\n             body: data,\n             credentials: 'same-origin'\n-        }).then(function (response) { return response.json(); });\n+        }).then(parseJsonResponse);\n     }\n \n     function showResult(html) {\n@@ -37,6 +51,13 @@\n         }\n     }\n \n+    function openDebugPanel() {\n+        var wrap = document.getElementById('debug-panel-wrap');\n+        if (wrap && !wrap.open) {\n+            wrap.open = true;\n+        }\n+    }\n+\n     function debugLog(message, detail) {\n         var panel = document.getElementById('debug-panel');\n         if (!panel) {\n@@ -45,7 +66,7 @@\n         var line = document.createElement('div');\n         line.className = 'debug-line';\n         var text = new Date().toLocaleTimeString('es-ES') + ' — ' + message;\n-        if (detail) {\n+        if (detail !== undefined && detail !== null && detail !== '') {\n             text += ' · ' + (typeof detail === 'string' ? detail : JSON.stringify(detail));\n         }\n         line.textContent = text;\n@@ -60,20 +81,24 @@\n         postForm(data).then(function (response) {\n             if (!response.ok) {\n                 debugLog('Diagnóstico falló', response.message || 'error');\n+                openDebugPanel();\n                 return;\n             }\n+            debugLog('JS', JS_VERSION);\n             debugLog('Modo', response.mode);\n             debugLog('URL servicio', response.service_url);\n             if (response.service && response.service.ok) {\n                 debugLog('Servicio OCR', 'OK');\n             } else if (response.service) {\n                 debugLog('Servicio OCR', response.service.message || 'no responde');\n+                openDebugPanel();\n             }\n             if (response.ollama) {\n                 debugLog('Ollama LAN', response.ollama.ok ? 'OK' : (response.ollama.message || 'no responde'));\n             }\n-        }).catch(function () {\n-            debugLog('Diagnóstico', 'error de red con FacturaScripts');\n+        }).catch(function (err) {\n+            debugLog('Diagnóstico', err && err.message ? err.message : 'error de red');\n+            openDebugPanel();\n         });\n     }\n \n@@ -172,7 +197,7 @@\n \n         if (galleryInput) {\n             galleryInput.addEventListener('change', function () {\n-                debugLog('Galería/archivo seleccionado', galleryInput.files.length ? galleryInput.files[0].name : 'vacío');\n+                debugLog('Galería/archivo', galleryInput.files.length ? galleryInput.files[0].name : 'vacío');\n                 if (!assignSelectedFile(galleryInput)) {\n                     showResult(formatErrorMessage({\n                         message: 'No se pudo leer el archivo seleccionado.',\n@@ -200,7 +225,6 @@\n         var uploadBtn = document.getElementById('upload-btn');\n         var manualBtn = document.getElementById('manual-btn');\n         var fileLabel = document.getElementById('file-label-hint');\n-        var fileField = document.getElementById('file-field');\n         var pickFileWrap = document.getElementById('pick-file-wrap');\n         var pickCameraWrap = document.getElementById('pick-camera-wrap');\n \n@@ -328,7 +352,14 @@\n         postForm(data).then(function (response) {\n             if (response.ok && response.item) {\n                 openConfirmModal(response.item);\n+                return;\n             }\n+            showResult(formatErrorMessage(response));\n+        }).catch(function (err) {\n+            showResult(formatErrorMessage({\n+                message: 'No se pudo cargar el documento de la cola.',\n+                hint: err && err.message ? err.message : ''\n+            }));\n         });\n     }\n \n@@ -343,12 +374,18 @@\n             data.append('action', 'manual');\n             data.append('doc_type', document.getElementById('doc-type').value);\n             btn.disabled = true;\n+            debugLog('Registro manual');\n             postForm(data).then(function (response) {\n                 if (response.ok && response.extracted) {\n                     openConfirmModal(response.extracted);\n                     return;\n                 }\n                 showResult(formatErrorMessage(response));\n+            }).catch(function (err) {\n+                showResult(formatErrorMessage({\n+                    message: 'Error al crear registro manual.',\n+                    hint: err && err.message ? err.message : ''\n+                }));\n             }).finally(function () {\n                 btn.disabled = false;\n             });\n@@ -405,6 +442,7 @@\n                     }\n                     if (!data.ok) {\n                         debugLog('Error upload', data.message);\n+                        openDebugPanel();\n                         showResult(formatErrorMessage(data));\n                         return;\n                     }\n@@ -422,9 +460,11 @@\n                 })\n                 .catch(function (err) {\n                     debugLog('Fetch error', err && err.message ? err.message : 'desconocido');\n+                    openDebugPanel();\n                     showResult(formatErrorMessage({\n                         message: 'Error de conexión con el servidor.',\n-                        reasons: ['Comprueba que Apache (XAMPP) esté activo.']\n+                        reasons: ['Comprueba que Apache (XAMPP) esté activo.'],\n+                        hint: err && err.message ? err.message : ''\n                     }));\n                 })\n                 .finally(function () {\n@@ -487,7 +527,7 @@\n         bindUploadForm();\n         bindReviewButtons();\n         bindConfirmActions();\n-        debugLog('Página lista', 'modo=' + expenseMode);\n+        debugLog('Página lista', 'modo=' + expenseMode + ' · v=' + JS_VERSION);\n         runDiagnose();\n     });\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,6 +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-add\">+    var JS_VERSION = '2026-06-25b';</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">     function route(path) {</div><div class=\"diff-line diff-ctx\">         var base = (window.ALIMPIEZAS_MOBILE &amp;&amp; window.ALIMPIEZAS_MOBILE.route) || '';</div><div class=\"diff-line diff-hunk\">@@ -19,6 +20,19 @@</div><div class=\"diff-line diff-ctx\">         return tokenInput ? tokenInput.value : '';</div><div class=\"diff-line diff-ctx\">     }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-add\">+    function parseJsonResponse(response) {</div><div class=\"diff-line diff-add\">+        return response.text().then(function (text) {</div><div class=\"diff-line diff-add\">+            if (!text) {</div><div class=\"diff-line diff-add\">+                throw new Error('Respuesta vacía del servidor (HTTP ' + response.status + ')');</div><div class=\"diff-line diff-add\">+            }</div><div class=\"diff-line diff-add\">+            try {</div><div class=\"diff-line diff-add\">+                return JSON.parse(text);</div><div class=\"diff-line diff-add\">+            } catch (err) {</div><div class=\"diff-line diff-add\">+                throw new Error('Respuesta no JSON: ' + text.substring(0, 180));</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\">+</div><div class=\"diff-line diff-ctx\">     function postForm(data) {</div><div class=\"diff-line diff-ctx\">         if (!data.has('multireqtoken')) {</div><div class=\"diff-line diff-ctx\">             data.append('multireqtoken', tokenValue());</div><div class=\"diff-line diff-hunk\">@@ -27,7 +41,7 @@</div><div class=\"diff-line diff-ctx\">             method: 'POST',</div><div class=\"diff-line diff-ctx\">             body: data,</div><div class=\"diff-line diff-ctx\">             credentials: 'same-origin'</div><div class=\"diff-line diff-del\">-        }).then(function (response) { return response.json(); });</div><div class=\"diff-line diff-add\">+        }).then(parseJsonResponse);</div><div class=\"diff-line diff-ctx\">     }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">     function showResult(html) {</div><div class=\"diff-line diff-hunk\">@@ -37,6 +51,13 @@</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-add\">+    function openDebugPanel() {</div><div class=\"diff-line diff-add\">+        var wrap = document.getElementById('debug-panel-wrap');</div><div class=\"diff-line diff-add\">+        if (wrap &amp;&amp; !wrap.open) {</div><div class=\"diff-line diff-add\">+            wrap.open = true;</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-ctx\">     function debugLog(message, detail) {</div><div class=\"diff-line diff-ctx\">         var panel = document.getElementById('debug-panel');</div><div class=\"diff-line diff-ctx\">         if (!panel) {</div><div class=\"diff-line diff-hunk\">@@ -45,7 +66,7 @@</div><div class=\"diff-line diff-ctx\">         var line = document.createElement('div');</div><div class=\"diff-line diff-ctx\">         line.className = 'debug-line';</div><div class=\"diff-line diff-ctx\">         var text = new Date().toLocaleTimeString('es-ES') + ' — ' + message;</div><div class=\"diff-line diff-del\">-        if (detail) {</div><div class=\"diff-line diff-add\">+        if (detail !== undefined &amp;&amp; detail !== null &amp;&amp; detail !== '') {</div><div class=\"diff-line diff-ctx\">             text += ' · ' + (typeof detail === 'string' ? detail : JSON.stringify(detail));</div><div class=\"diff-line diff-ctx\">         }</div><div class=\"diff-line diff-ctx\">         line.textContent = text;</div><div class=\"diff-line diff-hunk\">@@ -60,20 +81,24 @@</div><div class=\"diff-line diff-ctx\">         postForm(data).then(function (response) {</div><div class=\"diff-line diff-ctx\">             if (!response.ok) {</div><div class=\"diff-line diff-ctx\">                 debugLog('Diagnóstico falló', response.message || 'error');</div><div class=\"diff-line diff-add\">+                openDebugPanel();</div><div class=\"diff-line diff-ctx\">                 return;</div><div class=\"diff-line diff-ctx\">             }</div><div class=\"diff-line diff-add\">+            debugLog('JS', JS_VERSION);</div><div class=\"diff-line diff-ctx\">             debugLog('Modo', response.mode);</div><div class=\"diff-line diff-ctx\">             debugLog('URL servicio', response.service_url);</div><div class=\"diff-line diff-ctx\">             if (response.service &amp;&amp; response.service.ok) {</div><div class=\"diff-line diff-ctx\">                 debugLog('Servicio OCR', 'OK');</div><div class=\"diff-line diff-ctx\">             } else if (response.service) {</div><div class=\"diff-line diff-ctx\">                 debugLog('Servicio OCR', response.service.message || 'no responde');</div><div class=\"diff-line diff-add\">+                openDebugPanel();</div><div class=\"diff-line diff-ctx\">             }</div><div class=\"diff-line diff-ctx\">             if (response.ollama) {</div><div class=\"diff-line diff-ctx\">                 debugLog('Ollama LAN', response.ollama.ok ? 'OK' : (response.ollama.message || 'no responde'));</div><div class=\"diff-line diff-ctx\">             }</div><div class=\"diff-line diff-del\">-        }).catch(function () {</div><div class=\"diff-line diff-del\">-            debugLog('Diagnóstico', 'error de red con FacturaScripts');</div><div class=\"diff-line diff-add\">+        }).catch(function (err) {</div><div class=\"diff-line diff-add\">+            debugLog('Diagnóstico', err &amp;&amp; err.message ? err.message : 'error de red');</div><div class=\"diff-line diff-add\">+            openDebugPanel();</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-hunk\">@@ -172,7 +197,7 @@</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">         if (galleryInput) {</div><div class=\"diff-line diff-ctx\">             galleryInput.addEventListener('change', function () {</div><div class=\"diff-line diff-del\">-                debugLog('Galería/archivo seleccionado', galleryInput.files.length ? galleryInput.files[0].name : 'vacío');</div><div class=\"diff-line diff-add\">+                debugLog('Galería/archivo', galleryInput.files.length ? galleryInput.files[0].name : 'vacío');</div><div class=\"diff-line diff-ctx\">                 if (!assignSelectedFile(galleryInput)) {</div><div class=\"diff-line diff-ctx\">                     showResult(formatErrorMessage({</div><div class=\"diff-line diff-ctx\">                         message: 'No se pudo leer el archivo seleccionado.',</div><div class=\"diff-line diff-hunk\">@@ -200,7 +225,6 @@</div><div class=\"diff-line diff-ctx\">         var uploadBtn = document.getElementById('upload-btn');</div><div class=\"diff-line diff-ctx\">         var manualBtn = document.getElementById('manual-btn');</div><div class=\"diff-line diff-ctx\">         var fileLabel = document.getElementById('file-label-hint');</div><div class=\"diff-line diff-del\">-        var fileField = document.getElementById('file-field');</div><div class=\"diff-line diff-ctx\">         var pickFileWrap = document.getElementById('pick-file-wrap');</div><div class=\"diff-line diff-ctx\">         var pickCameraWrap = document.getElementById('pick-camera-wrap');</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-hunk\">@@ -328,7 +352,14 @@</div><div class=\"diff-line diff-ctx\">         postForm(data).then(function (response) {</div><div class=\"diff-line diff-ctx\">             if (response.ok &amp;&amp; response.item) {</div><div class=\"diff-line diff-ctx\">                 openConfirmModal(response.item);</div><div class=\"diff-line diff-add\">+                return;</div><div class=\"diff-line diff-ctx\">             }</div><div class=\"diff-line diff-add\">+            showResult(formatErrorMessage(response));</div><div class=\"diff-line diff-add\">+        }).catch(function (err) {</div><div class=\"diff-line diff-add\">+            showResult(formatErrorMessage({</div><div class=\"diff-line diff-add\">+                message: 'No se pudo cargar el documento de la cola.',</div><div class=\"diff-line diff-add\">+                hint: err &amp;&amp; err.message ? err.message : ''</div><div class=\"diff-line diff-add\">+            }));</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-hunk\">@@ -343,12 +374,18 @@</div><div class=\"diff-line diff-ctx\">             data.append('action', 'manual');</div><div class=\"diff-line diff-ctx\">             data.append('doc_type', document.getElementById('doc-type').value);</div><div class=\"diff-line diff-ctx\">             btn.disabled = true;</div><div class=\"diff-line diff-add\">+            debugLog('Registro manual');</div><div class=\"diff-line diff-ctx\">             postForm(data).then(function (response) {</div><div class=\"diff-line diff-ctx\">                 if (response.ok &amp;&amp; response.extracted) {</div><div class=\"diff-line diff-ctx\">                     openConfirmModal(response.extracted);</div><div class=\"diff-line diff-ctx\">                     return;</div><div class=\"diff-line diff-ctx\">                 }</div><div class=\"diff-line diff-ctx\">                 showResult(formatErrorMessage(response));</div><div class=\"diff-line diff-add\">+            }).catch(function (err) {</div><div class=\"diff-line diff-add\">+                showResult(formatErrorMessage({</div><div class=\"diff-line diff-add\">+                    message: 'Error al crear registro manual.',</div><div class=\"diff-line diff-add\">+                    hint: err &amp;&amp; err.message ? err.message : ''</div><div class=\"diff-line diff-add\">+                }));</div><div class=\"diff-line diff-ctx\">             }).finally(function () {</div><div class=\"diff-line diff-ctx\">                 btn.disabled = false;</div><div class=\"diff-line diff-ctx\">             });</div><div class=\"diff-line diff-hunk\">@@ -405,6 +442,7 @@</div><div class=\"diff-line diff-ctx\">                     }</div><div class=\"diff-line diff-ctx\">                     if (!data.ok) {</div><div class=\"diff-line diff-ctx\">                         debugLog('Error upload', data.message);</div><div class=\"diff-line diff-add\">+                        openDebugPanel();</div><div class=\"diff-line diff-ctx\">                         showResult(formatErrorMessage(data));</div><div class=\"diff-line diff-ctx\">                         return;</div><div class=\"diff-line diff-ctx\">                     }</div><div class=\"diff-line diff-hunk\">@@ -422,9 +460,11 @@</div><div class=\"diff-line diff-ctx\">                 })</div><div class=\"diff-line diff-ctx\">                 .catch(function (err) {</div><div class=\"diff-line diff-ctx\">                     debugLog('Fetch error', err &amp;&amp; err.message ? err.message : 'desconocido');</div><div class=\"diff-line diff-add\">+                    openDebugPanel();</div><div class=\"diff-line diff-ctx\">                     showResult(formatErrorMessage({</div><div class=\"diff-line diff-ctx\">                         message: 'Error de conexión con el servidor.',</div><div class=\"diff-line diff-del\">-                        reasons: ['Comprueba que Apache (XAMPP) esté activo.']</div><div class=\"diff-line diff-add\">+                        reasons: ['Comprueba que Apache (XAMPP) esté activo.'],</div><div class=\"diff-line diff-add\">+                        hint: err &amp;&amp; err.message ? err.message : ''</div><div class=\"diff-line diff-ctx\">                     }));</div><div class=\"diff-line diff-ctx\">                 })</div><div class=\"diff-line diff-ctx\">                 .finally(function () {</div><div class=\"diff-line diff-hunk\">@@ -487,7 +527,7 @@</div><div class=\"diff-line diff-ctx\">         bindUploadForm();</div><div class=\"diff-line diff-ctx\">         bindReviewButtons();</div><div class=\"diff-line diff-ctx\">         bindConfirmActions();</div><div class=\"diff-line diff-del\">-        debugLog('Página lista', 'modo=' + expenseMode);</div><div class=\"diff-line diff-add\">+        debugLog('Página lista', 'modo=' + expenseMode + ' · v=' + JS_VERSION);</div><div class=\"diff-line diff-ctx\">         runDiagnose();</div><div class=\"diff-line diff-ctx\">     });</div><div class=\"diff-line diff-ctx\"> })();</div></div>","linesAdded":48,"linesRemoved":8,"status":"pending"},"pending":2},"at":1782349569016},{"event":"tool_activity","data":{"activity":{"id":"tool_11bfabdf-3ad1-4e4d-904e-8c735442ee1","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:06:10.444Z"}},"at":1782349570444},{"event":"file_change","data":{"sessionId":"cs-1782349514621-fwmksd","change":{"id":"ch-3","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@@ -138,8 +138,29 @@\n     cursor: pointer;\n     z-index: 2;\n     font-size: 16px;\n+    margin: 0;\n+    padding: 0;\n+    border: 0;\n }\n \n+.debug-panel {\n+    max-height: 220px;\n+    overflow-y: auto;\n+    background: #f8fafc;\n+    border: 1px solid #e5e7eb;\n+    border-radius: 8px;\n+    padding: 8px 10px;\n+    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n+    font-size: 0.72rem;\n+    line-height: 1.45;\n+}\n+\n+.debug-line + .debug-line {\n+    margin-top: 4px;\n+    padding-top: 4px;\n+    border-top: 1px dashed #e5e7eb;\n+}\n+\n .file-picker-wrap.is-selected .btn {\n     border-color: #198754;\n     color: #198754;","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\">@@ -138,8 +138,29 @@</div><div class=\"diff-line diff-ctx\">     cursor: pointer;</div><div class=\"diff-line diff-ctx\">     z-index: 2;</div><div class=\"diff-line diff-ctx\">     font-size: 16px;</div><div class=\"diff-line diff-add\">+    margin: 0;</div><div class=\"diff-line diff-add\">+    padding: 0;</div><div class=\"diff-line diff-add\">+    border: 0;</div><div class=\"diff-line diff-ctx\"> }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-add\">+.debug-panel {</div><div class=\"diff-line diff-add\">+    max-height: 220px;</div><div class=\"diff-line diff-add\">+    overflow-y: auto;</div><div class=\"diff-line diff-add\">+    background: #f8fafc;</div><div class=\"diff-line diff-add\">+    border: 1px solid #e5e7eb;</div><div class=\"diff-line diff-add\">+    border-radius: 8px;</div><div class=\"diff-line diff-add\">+    padding: 8px 10px;</div><div class=\"diff-line diff-add\">+    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;</div><div class=\"diff-line diff-add\">+    font-size: 0.72rem;</div><div class=\"diff-line diff-add\">+    line-height: 1.45;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.debug-line + .debug-line {</div><div class=\"diff-line diff-add\">+    margin-top: 4px;</div><div class=\"diff-line diff-add\">+    padding-top: 4px;</div><div class=\"diff-line diff-add\">+    border-top: 1px dashed #e5e7eb;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-ctx\"> .file-picker-wrap.is-selected .btn {</div><div class=\"diff-line diff-ctx\">     border-color: #198754;</div><div class=\"diff-line diff-ctx\">     color: #198754;</div></div>","linesAdded":21,"linesRemoved":0,"status":"pending"},"pending":3},"at":1782349570444},{"event":"tool_activity","data":{"activity":{"id":"tool_b306160a-57d2-49dc-8135-404f1fb9f67","name":"shell","status":"completed","summary":"shell","detail":"Copy-Item -Force \"C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js\" \"C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Assets\\JS\\mobile.js\"; Copy-Item -Force \"C:\\xampp\\htdocs\\ALimpiezas\\Plugi…","at":"2026-06-25T01:06:13.097Z"}},"at":1782349573097},{"event":"tool_activity","data":{"activity":{"id":"tool_ad82e0dd-dcc7-413a-bf9d-8e726ce63bd","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas","detail":"","at":"2026-06-25T01:06:15.410Z"}},"at":1782349575410},{"event":"tool_activity","data":{"activity":{"id":"tool_ca0a3110-b876-4fe7-86da-dacb74d5450","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:06:15.441Z"}},"at":1782349575441},{"event":"tool_activity","data":{"activity":{"id":"tool_ad82e0dd-dcc7-413a-bf9d-8e726ce63bd","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas","detail":"","at":"2026-06-25T01:06:15.599Z"}},"at":1782349575599},{"event":"tool_activity","data":{"activity":{"id":"tool_ca0a3110-b876-4fe7-86da-dacb74d5450","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:06:15.662Z"}},"at":1782349575662},{"event":"tool_activity","data":{"activity":{"id":"tool_cb56311d-40ac-468c-90b5-8c385b5681e","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js","detail":"","at":"2026-06-25T01:06:17.263Z"}},"at":1782349577263},{"event":"tool_activity","data":{"activity":{"id":"tool_dc48473f-b149-4a79-89f3-279ebcb39aa","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\View\\Master\\MobileLayout.html.twig","detail":"","at":"2026-06-25T01:06:17.445Z"}},"at":1782349577445},{"event":"tool_activity","data":{"activity":{"id":"tool_d7fadb5a-eb0d-4de7-9ec1-763fecdda9b","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:06:19.057Z"}},"at":1782349579057},{"event":"text","data":{"chunk":"El service worker cachea el JS antiguo; lo actualizo para forzar la recarga.\n"},"at":1782349581506},{"event":"tool_activity","data":{"activity":{"id":"tool_b085516c-bdc2-4a29-baa9-a7389c2866b","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas","detail":"","at":"2026-06-25T01:06:20.500Z"}},"at":1782349580500},{"event":"tool_activity","data":{"activity":{"id":"tool_4e4608a9-02ba-4a14-a9b7-0898c48b632","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:06:21.494Z"}},"at":1782349581494},{"event":"status","data":{"text":"Límite de 80 herramientas alcanzado — finalizando con lo obtenido."},"at":1782349581494},{"event":"status","data":{"text":"Turno añadido al transcript del IDE (reabrir chat para verlo)"},"at":1782349581503},{"event":"done","data":{"ok":true,"bridgeSessionId":"bs-1782349581501-9quw3b","agentId":"agent-db24b4ef-2ed7-4ef1-a64b-a3d4d84f72fa","runId":"run-c9ce08ee-440a-4661-be8e-8ae647e43ee1","status":"cancelled","result":"","changeSessionId":"cs-1782349514621-fwmksd","pendingChanges":3,"changes":[{"id":"ch-1","callId":"tool_42e82e1e-114d-4296-96fb-3addb144fb8","path":"Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig\n@@ -32,22 +32,22 @@\n                     <label class=\"form-label\">Documento <span class=\"text-muted\" id=\"file-label-hint\">(obligatorio para OCR)</span></label>\n                     <input type=\"file\" name=\"image\" id=\"file-input-active\" class=\"visually-hidden\" tabindex=\"-1\" aria-hidden=\"true\" accept=\"image/*,.pdf,application/pdf,.heic,.heif\">\n                     <div class=\"d-grid gap-2 mb-2\">\n-                        <label class=\"file-picker-wrap mb-0\" id=\"pick-file-wrap\">\n-                            <input type=\"file\" id=\"file-input-gallery\" class=\"visually-hidden\"\n-                                   accept=\"image/*,.pdf,application/pdf,.heic,.heif\"\n-                                   aria-label=\"Elegir archivo o galería\">\n+                        <div class=\"file-picker-wrap\" id=\"pick-file-wrap\">\n                             <span class=\"btn btn-outline-secondary btn-lg w-100\">\n                                 <i class=\"fa-solid fa-folder-open me-2\"></i>Elegir archivo o galería\n                             </span>\n-                        </label>\n-                        <label class=\"file-picker-wrap mb-0\" id=\"pick-camera-wrap\">\n-                            <input type=\"file\" id=\"file-input-camera\" class=\"visually-hidden\"\n-                                   accept=\"image/*\" capture=\"environment\"\n-                                   aria-label=\"Hacer foto con cámara\">\n+                            <input type=\"file\" id=\"file-input-gallery\" class=\"file-picker-overlay\"\n+                                   accept=\"image/*,.pdf,application/pdf,.heic,.heif\"\n+                                   aria-label=\"Elegir archivo o galería\">\n+                        </div>\n+                        <div class=\"file-picker-wrap\" id=\"pick-camera-wrap\">\n                             <span class=\"btn btn-outline-secondary btn-lg w-100\">\n                                 <i class=\"fa-solid fa-camera me-2\"></i>Hacer foto\n                             </span>\n-                        </label>\n+                            <input type=\"file\" id=\"file-input-camera\" class=\"file-picker-overlay\"\n+                                   accept=\"image/*\" capture=\"environment\"\n+                                   aria-label=\"Hacer foto con cámara\">\n+                        </div>\n                     </div>\n                     <div id=\"file-selected-name\" class=\"small text-muted\">Ningún documento seleccionado</div>\n                 </div>\n@@ -61,7 +61,7 @@\n                 </div>\n             </form>\n             <div id=\"upload-result\" class=\"mt-3\"></div>\n-            <details class=\"mt-3 debug-panel-wrap\">\n+            <details class=\"mt-3 debug-panel-wrap\" id=\"debug-panel-wrap\">\n                 <summary class=\"small text-muted\">Debug conexión OCR/IA</summary>\n                 <div id=\"debug-panel\" class=\"debug-panel small mt-2\"></div>\n             </details>","linesAdded":11,"linesRemoved":11,"status":"pending"},{"id":"ch-2","callId":"tool_fe6df60a-61b6-4d63-83c2-f5dd044f6d3","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,6 +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-25b';\n \n     function route(path) {\n         var base = (window.ALIMPIEZAS_MOBILE && window.ALIMPIEZAS_MOBILE.route) || '';\n@@ -19,6 +20,19 @@\n         return tokenInput ? tokenInput.value : '';\n     }\n \n+    function parseJsonResponse(response) {\n+        return response.text().then(function (text) {\n+            if (!text) {\n+                throw new Error('Respuesta vacía del servidor (HTTP ' + response.status + ')');\n+            }\n+            try {\n+                return JSON.parse(text);\n+            } catch (err) {\n+                throw new Error('Respuesta no JSON: ' + text.substring(0, 180));\n+            }\n+        });\n+    }\n+\n     function postForm(data) {\n         if (!data.has('multireqtoken')) {\n             data.append('multireqtoken', tokenValue());\n@@ -27,7 +41,7 @@\n             method: 'POST',\n             body: data,\n             credentials: 'same-origin'\n-        }).then(function (response) { return response.json(); });\n+        }).then(parseJsonResponse);\n     }\n \n     function showResult(html) {\n@@ -37,6 +51,13 @@\n         }\n     }\n \n+    function openDebugPanel() {\n+        var wrap = document.getElementById('debug-panel-wrap');\n+        if (wrap && !wrap.open) {\n+            wrap.open = true;\n+        }\n+    }\n+\n     function debugLog(message, detail) {\n         var panel = document.getElementById('debug-panel');\n         if (!panel) {\n@@ -45,7 +66,7 @@\n         var line = document.createElement('div');\n         line.className = 'debug-line';\n         var text = new Date().toLocaleTimeString('es-ES') + ' — ' + message;\n-        if (detail) {\n+        if (detail !== undefined && detail !== null && detail !== '') {\n             text += ' · ' + (typeof detail === 'string' ? detail : JSON.stringify(detail));\n         }\n         line.textContent = text;\n@@ -60,20 +81,24 @@\n         postForm(data).then(function (response) {\n             if (!response.ok) {\n                 debugLog('Diagnóstico falló', response.message || 'error');\n+                openDebugPanel();\n                 return;\n             }\n+            debugLog('JS', JS_VERSION);\n             debugLog('Modo', response.mode);\n             debugLog('URL servicio', response.service_url);\n             if (response.service && response.service.ok) {\n                 debugLog('Servicio OCR', 'OK');\n             } else if (response.service) {\n                 debugLog('Servicio OCR', response.service.message || 'no responde');\n+                openDebugPanel();\n             }\n             if (response.ollama) {\n                 debugLog('Ollama LAN', response.ollama.ok ? 'OK' : (response.ollama.message || 'no responde'));\n             }\n-        }).catch(function () {\n-            debugLog('Diagnóstico', 'error de red con FacturaScripts');\n+        }).catch(function (err) {\n+            debugLog('Diagnóstico', err && err.message ? err.message : 'error de red');\n+            openDebugPanel();\n         });\n     }\n \n@@ -172,7 +197,7 @@\n \n         if (galleryInput) {\n             galleryInput.addEventListener('change', function () {\n-                debugLog('Galería/archivo seleccionado', galleryInput.files.length ? galleryInput.files[0].name : 'vacío');\n+                debugLog('Galería/archivo', galleryInput.files.length ? galleryInput.files[0].name : 'vacío');\n                 if (!assignSelectedFile(galleryInput)) {\n                     showResult(formatErrorMessage({\n                         message: 'No se pudo leer el archivo seleccionado.',\n@@ -200,7 +225,6 @@\n         var uploadBtn = document.getElementById('upload-btn');\n         var manualBtn = document.getElementById('manual-btn');\n         var fileLabel = document.getElementById('file-label-hint');\n-        var fileField = document.getElementById('file-field');\n         var pickFileWrap = document.getElementById('pick-file-wrap');\n         var pickCameraWrap = document.getElementById('pick-camera-wrap');\n \n@@ -328,7 +352,14 @@\n         postForm(data).then(function (response) {\n             if (response.ok && response.item) {\n                 openConfirmModal(response.item);\n+                return;\n             }\n+            showResult(formatErrorMessage(response));\n+        }).catch(function (err) {\n+            showResult(formatErrorMessage({\n+                message: 'No se pudo cargar el documento de la cola.',\n+                hint: err && err.message ? err.message : ''\n+            }));\n         });\n     }\n \n@@ -343,12 +374,18 @@\n             data.append('action', 'manual');\n             data.append('doc_type', document.getElementById('doc-type').value);\n             btn.disabled = true;\n+            debugLog('Registro manual');\n             postForm(data).then(function (response) {\n                 if (response.ok && response.extracted) {\n                     openConfirmModal(response.extracted);\n                     return;\n                 }\n                 showResult(formatErrorMessage(response));\n+            }).catch(function (err) {\n+                showResult(formatErrorMessage({\n+                    message: 'Error al crear registro manual.',\n+                    hint: err && err.message ? err.message : ''\n+                }));\n             }).finally(function () {\n                 btn.disabled = false;\n             });\n@@ -405,6 +442,7 @@\n                     }\n                     if (!data.ok) {\n                         debugLog('Error upload', data.message);\n+                        openDebugPanel();\n                         showResult(formatErrorMessage(data));\n                         return;\n                     }\n@@ -422,9 +460,11 @@\n                 })\n                 .catch(function (err) {\n                     debugLog('Fetch error', err && err.message ? err.message : 'desconocido');\n+                    openDebugPanel();\n                     showResult(formatErrorMessage({\n                         message: 'Error de conexión con el servidor.',\n-                        reasons: ['Comprueba que Apache (XAMPP) esté activo.']\n+                        reasons: ['Comprueba que Apache (XAMPP) esté activo.'],\n+                        hint: err && err.message ? err.message : ''\n                     }));\n                 })\n                 .finally(function () {\n@@ -487,7 +527,7 @@\n         bindUploadForm();\n         bindReviewButtons();\n         bindConfirmActions();\n-        debugLog('Página lista', 'modo=' + expenseMode);\n+        debugLog('Página lista', 'modo=' + expenseMode + ' · v=' + JS_VERSION);\n         runDiagnose();\n     });\n })();","linesAdded":48,"linesRemoved":8,"status":"pending"},{"id":"ch-3","callId":"tool_11bfabdf-3ad1-4e4d-904e-8c735442ee1","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@@ -138,8 +138,29 @@\n     cursor: pointer;\n     z-index: 2;\n     font-size: 16px;\n+    margin: 0;\n+    padding: 0;\n+    border: 0;\n }\n \n+.debug-panel {\n+    max-height: 220px;\n+    overflow-y: auto;\n+    background: #f8fafc;\n+    border: 1px solid #e5e7eb;\n+    border-radius: 8px;\n+    padding: 8px 10px;\n+    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n+    font-size: 0.72rem;\n+    line-height: 1.45;\n+}\n+\n+.debug-line + .debug-line {\n+    margin-top: 4px;\n+    padding-top: 4px;\n+    border-top: 1px dashed #e5e7eb;\n+}\n+\n .file-picker-wrap.is-selected .btn {\n     border-color: #198754;\n     color: #198754;","linesAdded":21,"linesRemoved":0,"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 el hilo: reviso los archivos del OCR móvil ya identificados y el error al registrar gastos.\nEl servicio OCR res"},"at":1782349581504}]}
