{"id":"cs-1782350776477-41icgexh","status":"done","createdAt":1782350776477,"finishedAt":1782350833095,"meta":{"projectName":"ALimpiezas","projectPath":"C:\\xampp\\htdocs\\ALimpiezas","messagePreview":"Se hs cortado tu respuesta.","phaseId":null},"events":[{"event":"status","data":{"text":"Modo IDE / sesión — completo (80 herramientas, uso selectivo)"},"at":1782350776480},{"event":"status","data":{"text":"Agente"},"at":1782350776481},{"event":"status","data":{"text":"Modo revisión: snapshot local por archivo"},"at":1782350776481},{"event":"status","data":{"text":"Contexto del IDE cargado"},"at":1782350776482},{"event":"agent","data":{"agentId":"agent-9cc504bf-bc48-4e49-adc3-52af03f84bef","projectPath":"C:\\xampp\\htdocs\\ALimpiezas"},"at":1782350776482},{"event":"changes","data":{"sessionId":"cs-1782350776480-l80a0v","pending":0},"at":1782350776482},{"event":"status","data":{"text":"Procesando…"},"at":1782350776535},{"event":"tool_activity","data":{"activity":{"id":"tool_a9d2d7b1-48ce-40d1-95d7-93ca5235973","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:26:19.470Z"}},"at":1782350779470},{"event":"tool_activity","data":{"activity":{"id":"tool_388fd9c5-ebc7-4f74-8c98-4b97ec6910b","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:26:19.473Z"}},"at":1782350779473},{"event":"tool_activity","data":{"activity":{"id":"tool_94eeddec-fcbe-467e-9dab-4dfaeefecf7","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:26:19.540Z"}},"at":1782350779540},{"event":"tool_activity","data":{"activity":{"id":"tool_51347cf9-412b-4670-9384-1b3c9e62534","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:26:19.628Z"}},"at":1782350779628},{"event":"tool_activity","data":{"activity":{"id":"tool_a9d2d7b1-48ce-40d1-95d7-93ca5235973","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:26:19.664Z"}},"at":1782350779664},{"event":"text","data":{"chunk":"Continúo donde se cortó: reviso el estado actual de la extracción fiscal con IA y la UI de confirmación móvil.\n"},"at":1782350833096},{"event":"tool_activity","data":{"activity":{"id":"tool_388fd9c5-ebc7-4f74-8c98-4b97ec6910b","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:26:19.669Z"}},"at":1782350779669},{"event":"tool_activity","data":{"activity":{"id":"tool_94eeddec-fcbe-467e-9dab-4dfaeefecf7","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:26:19.690Z"}},"at":1782350779690},{"event":"tool_activity","data":{"activity":{"id":"tool_51347cf9-412b-4670-9384-1b3c9e62534","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:26:19.797Z"}},"at":1782350779797},{"event":"tool_activity","data":{"activity":{"id":"tool_525f2486-e2f5-4b1c-8a62-9403beebf46","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py","detail":"","at":"2026-06-25T01:26:20.782Z"}},"at":1782350780782},{"event":"tool_activity","data":{"activity":{"id":"tool_7c954fd2-0720-4049-9a8f-2404bf5a564","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:26:20.782Z"}},"at":1782350780782},{"event":"tool_activity","data":{"activity":{"id":"tool_f4d53c36-675c-4a1a-a990-7a8df75f60e","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\docs\\MANUAL-AGENTE-IA.md","detail":"","at":"2026-06-25T01:26:20.783Z"}},"at":1782350780783},{"event":"tool_activity","data":{"activity":{"id":"tool_44e8e05d-4513-4fb0-b3b0-c5691d6d2d0","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:26:20.784Z"}},"at":1782350780784},{"event":"tool_activity","data":{"activity":{"id":"tool_525f2486-e2f5-4b1c-8a62-9403beebf46","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py","detail":"","at":"2026-06-25T01:26:20.905Z"}},"at":1782350780905},{"event":"tool_activity","data":{"activity":{"id":"tool_f4d53c36-675c-4a1a-a990-7a8df75f60e","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\docs\\MANUAL-AGENTE-IA.md","detail":"","at":"2026-06-25T01:26:20.911Z"}},"at":1782350780911},{"event":"tool_activity","data":{"activity":{"id":"tool_44e8e05d-4513-4fb0-b3b0-c5691d6d2d0","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:26:20.985Z"}},"at":1782350780985},{"event":"tool_activity","data":{"activity":{"id":"tool_7c954fd2-0720-4049-9a8f-2404bf5a564","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:26:21.016Z"}},"at":1782350781016},{"event":"tool_activity","data":{"activity":{"id":"tool_b27ccc65-9f72-4633-9a29-d13eef8d00f","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:26:22.558Z"}},"at":1782350782558},{"event":"tool_activity","data":{"activity":{"id":"tool_4b136f78-1964-42f2-9d7b-97331963bcd","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:26:22.559Z"}},"at":1782350782559},{"event":"tool_activity","data":{"activity":{"id":"tool_90031d1b-cb45-4d90-b94a-5ac7b041966","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:26:22.560Z"}},"at":1782350782560},{"event":"tool_activity","data":{"activity":{"id":"tool_6e3be842-4a13-4b84-81b2-f416b094cd7","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:26:22.560Z"}},"at":1782350782560},{"event":"tool_activity","data":{"activity":{"id":"tool_b27ccc65-9f72-4633-9a29-d13eef8d00f","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\JS\\mobile.js","detail":"","at":"2026-06-25T01:26:22.677Z"}},"at":1782350782677},{"event":"tool_activity","data":{"activity":{"id":"tool_90031d1b-cb45-4d90-b94a-5ac7b041966","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:26:22.709Z"}},"at":1782350782709},{"event":"tool_activity","data":{"activity":{"id":"tool_4b136f78-1964-42f2-9d7b-97331963bcd","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:26:22.722Z"}},"at":1782350782722},{"event":"tool_activity","data":{"activity":{"id":"tool_6e3be842-4a13-4b84-81b2-f416b094cd7","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:26:22.759Z"}},"at":1782350782759},{"event":"tool_activity","data":{"activity":{"id":"tool_247ce532-7de9-428a-998f-6275419aad3","name":"read","status":"running","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:26:23.812Z"}},"at":1782350783812},{"event":"tool_activity","data":{"activity":{"id":"tool_88ff1f6b-8731-41dd-938a-542bae84451","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\MobileConfig.php","detail":"","at":"2026-06-25T01:26:23.813Z"}},"at":1782350783813},{"event":"tool_activity","data":{"activity":{"id":"tool_247ce532-7de9-428a-998f-6275419aad3","name":"read","status":"completed","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:26:23.915Z"}},"at":1782350783915},{"event":"tool_activity","data":{"activity":{"id":"tool_88ff1f6b-8731-41dd-938a-542bae84451","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\MobileConfig.php","detail":"","at":"2026-06-25T01:26:23.917Z"}},"at":1782350783917},{"event":"tool_activity","data":{"activity":{"id":"tool_0125459a-04a5-4037-b15d-1675b5edf67","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:26:23.941Z"}},"at":1782350783941},{"event":"tool_activity","data":{"activity":{"id":"tool_0cb59da8-ff4f-4da0-98a8-5a0a06f763c","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas","detail":"","at":"2026-06-25T01:26:24.030Z"}},"at":1782350784030},{"event":"tool_activity","data":{"activity":{"id":"tool_0125459a-04a5-4037-b15d-1675b5edf67","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:26:24.037Z"}},"at":1782350784037},{"event":"tool_activity","data":{"activity":{"id":"tool_0cb59da8-ff4f-4da0-98a8-5a0a06f763c","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas","detail":"","at":"2026-06-25T01:26:24.240Z"}},"at":1782350784240},{"event":"tool_activity","data":{"activity":{"id":"tool_ec70e910-8376-43d9-905b-159f506433f","name":"read","status":"running","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:26:29.107Z"}},"at":1782350789107},{"event":"tool_activity","data":{"activity":{"id":"tool_8b375a0c-fd1c-4dc1-9e44-386b29d18b4","name":"glob","status":"running","summary":"glob: ","detail":"","at":"2026-06-25T01:26:29.108Z"}},"at":1782350789108},{"event":"tool_activity","data":{"activity":{"id":"tool_bca3b248-1cfa-48a6-a861-f2a551a483f","name":"read","status":"running","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Controller\\MobileExpenses.php","detail":"","at":"2026-06-25T01:26:29.124Z"}},"at":1782350789124},{"event":"tool_activity","data":{"activity":{"id":"tool_ec70e910-8376-43d9-905b-159f506433f","name":"read","status":"completed","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:26:29.207Z"}},"at":1782350789207},{"event":"tool_activity","data":{"activity":{"id":"tool_bca3b248-1cfa-48a6-a861-f2a551a483f","name":"read","status":"completed","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Controller\\MobileExpenses.php","detail":"","at":"2026-06-25T01:26:29.217Z"}},"at":1782350789217},{"event":"tool_activity","data":{"activity":{"id":"tool_8b375a0c-fd1c-4dc1-9e44-386b29d18b4","name":"glob","status":"completed","summary":"glob: ","detail":"","at":"2026-06-25T01:26:29.251Z"}},"at":1782350789251},{"event":"tool_activity","data":{"activity":{"id":"tool_c696182b-6a80-4b83-82b0-7ebc65713f4","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile","detail":"","at":"2026-06-25T01:26:29.459Z"}},"at":1782350789459},{"event":"tool_activity","data":{"activity":{"id":"tool_c303e0e8-afd0-410d-81b7-638a229f883","name":"read","status":"running","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js","detail":"","at":"2026-06-25T01:26:34.817Z"}},"at":1782350794817},{"event":"tool_activity","data":{"activity":{"id":"tool_8054e092-b671-4aee-a5fb-f06159cef90","name":"grep","status":"running","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Lib","detail":"","at":"2026-06-25T01:26:34.818Z"}},"at":1782350794818},{"event":"tool_activity","data":{"activity":{"id":"tool_abaeba26-edd9-4a86-82c0-8379b66fe44","name":"read","status":"running","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:26:34.819Z"}},"at":1782350794819},{"event":"tool_activity","data":{"activity":{"id":"tool_c303e0e8-afd0-410d-81b7-638a229f883","name":"read","status":"completed","summary":"read: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\sw.js","detail":"","at":"2026-06-25T01:26:34.920Z"}},"at":1782350794920},{"event":"tool_activity","data":{"activity":{"id":"tool_abaeba26-edd9-4a86-82c0-8379b66fe44","name":"read","status":"completed","summary":"read: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:26:34.921Z"}},"at":1782350794921},{"event":"tool_activity","data":{"activity":{"id":"tool_8054e092-b671-4aee-a5fb-f06159cef90","name":"grep","status":"completed","summary":"grep: C:\\xampp\\htdocs\\ALimpiezas\\Dinamic\\Lib","detail":"","at":"2026-06-25T01:26:35.005Z"}},"at":1782350795005},{"event":"text","data":{"chunk":"Completando la implementación cortada: método PHP faltante, UI fiscal moderna, Ollama en modo OCR y despliegue.\n"},"at":1782350833096},{"event":"tool_activity","data":{"activity":{"id":"tool_9f833b82-3775-441f-b87f-3b01fdd8c25","name":"updateTodos","status":"completed","summary":"updateTodos","detail":"{\"todos\":[{\"content\":\"AiExpenseProcessor: enrichFiscalValidation + formatItem + ollama_model\",\"status\":\"inProgress\"},{\"…","at":"2026-06-25T01:26:42.937Z"}},"at":1782350802937},{"event":"tool_activity","data":{"activity":{"id":"tool_c5ad3189-16bb-4e1a-a16c-e89401635e7","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:26:47.644Z"}},"at":1782350807644},{"event":"file_change","data":{"sessionId":"cs-1782350776480-l80a0v","change":{"id":"ch-1","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@@ -340,19 +340,38 @@\n \n     private static function formatItem(AiExpenseQueue $queue, array $extracted = []): array\n     {\n+        $warnings = $extracted['validation_warnings'] ?? $extracted['warnings'] ?? [];\n+        $errors = $extracted['validation_errors'] ?? [];\n+\n         return [\n             'id' => $queue->id,\n             'doc_type' => $queue->doc_type,\n+            'document_kind' => $extracted['document_kind'] ?? self::inferDocumentKind($queue->doc_type),\n             'supplier_name' => $queue->supplier_name,\n             'supplier_cif' => $queue->supplier_cif,\n+            'counterparty_address' => $extracted['counterparty_address'] ?? '',\n             'invoice_number' => $queue->invoice_number,\n+            'invoice_series' => $extracted['invoice_series'] ?? '',\n             'invoice_date' => $queue->invoice_date,\n+            'operation_date' => $extracted['operation_date'] ?? $queue->invoice_date,\n+            'concept' => $extracted['concept'] ?? '',\n             'base_amount' => $queue->base_amount,\n+            'tax_rate' => $extracted['tax_rate'] ?? 21,\n             'tax_amount' => $queue->tax_amount,\n+            'irpf_rate' => $extracted['irpf_rate'] ?? null,\n+            'irpf_amount' => $extracted['irpf_amount'] ?? null,\n             'total_amount' => $queue->total_amount,\n+            'currency' => $extracted['currency'] ?? 'EUR',\n+            'payment_method' => $extracted['payment_method'] ?? '',\n+            'is_rectifying' => (bool) ($extracted['is_rectifying'] ?? false),\n+            'deductible' => (bool) ($extracted['deductible'] ?? $queue->doc_type === 'expense'),\n             'confidence' => $queue->confidence,\n+            'fiscal_compliance' => $extracted['fiscal_compliance'] ?? null,\n+            'field_status' => $extracted['field_status'] ?? [],\n+            'validation_errors' => $errors,\n+            'validation_warnings' => $warnings,\n             'notes' => $extracted['notes'] ?? '',\n-            'warnings' => $extracted['warnings'] ?? [],\n+            'warnings' => $warnings,\n             'ocr_engine' => $extracted['ocr_engine'] ?? '',\n             'image_url' => $queue->image_path\n                 ? Tools::config('route') . '/' . $queue->image_path\n@@ -360,6 +379,108 @@\n         ];\n     }\n \n+    private static function inferDocumentKind(string $docType): string\n+    {\n+        return $docType === 'issued' ? 'issued_invoice' : 'expense_received';\n+    }\n+\n+    /** @param array<string, mixed> $data */\n+    private static function enrichFiscalValidation(array $data): array\n+    {\n+        if (!empty($data['_error'])) {\n+            return $data;\n+        }\n+\n+        if (empty($data['supplier_name']) && !empty($data['counterparty_name'])) {\n+            $data['supplier_name'] = trim((string) $data['counterparty_name']);\n+        }\n+        if (empty($data['supplier_cif']) && !empty($data['counterparty_cif'])) {\n+            $data['supplier_cif'] = trim((string) $data['counterparty_cif']);\n+        }\n+\n+        $cif = strtoupper(trim((string) ($data['supplier_cif'] ?? '')));\n+        if ($cif !== '') {\n+            $data['supplier_cif'] = $cif;\n+        }\n+\n+        if (!isset($data['field_status']) || !is_array($data['field_status'])) {\n+            $data['field_status'] = [];\n+        }\n+\n+        if ($cif !== '') {\n+            $validator = new FiscalNumberValidator();\n+            $data['field_status']['counterparty_cif'] = $validator->isValid($cif) ? 'ok' : 'review';\n+        } elseif (!isset($data['field_status']['counterparty_cif'])) {\n+            $data['field_status']['counterparty_cif'] = 'review';\n+        }\n+\n+        if (empty($data['field_status'])) {\n+            $data['field_status'] = self::computeFieldStatus($data);\n+        }\n+\n+        if (!isset($data['fiscal_compliance'])) {\n+            $data['fiscal_compliance'] = self::computeCompliance($data['field_status']);\n+        }\n+\n+        $warnings = $data['validation_warnings'] ?? $data['warnings'] ?? [];\n+        if (!is_array($warnings)) {\n+            $warnings = [];\n+        }\n+\n+        if ($cif !== '' && ($data['field_status']['counterparty_cif'] ?? '') === 'review') {\n+            $warnings[] = 'El NIF/CIF no supera la validación algorítmica española. Revísalo.';\n+        }\n+\n+        $data['validation_warnings'] = array_values(array_unique($warnings));\n+        $data['warnings'] = $data['validation_warnings'];\n+\n+        return $data;\n+    }\n+\n+    /** @param array<string, mixed> $data */\n+    private static function computeFieldStatus(array $data): array\n+    {\n+        $docKind = $data['document_kind'] ?? 'expense_received';\n+        $isIssued = $docKind === 'issued_invoice' || ($data['doc_type'] ?? '') === 'issued';\n+\n+        $required = $isIssued\n+            ? ['counterparty_name', 'invoice_number', 'invoice_date', 'total_amount']\n+            : ['counterparty_name', 'invoice_date', 'total_amount'];\n+\n+        $status = [];\n+        foreach ($required as $field) {\n+            $value = $data[$field] ?? null;\n+            if ($field === 'counterparty_name' && empty($value)) {\n+                $value = $data['supplier_name'] ?? null;\n+            }\n+            if ($field === 'total_amount' && empty($value)) {\n+                $value = $data['total_amount'] ?? null;\n+            }\n+            $status[$field] = ($value !== null && $value !== '' && $value !== 0) ? 'ok' : 'missing';\n+        }\n+\n+        return $status;\n+    }\n+\n+    /** @param array<string, string> $fieldStatus */\n+    private static function computeCompliance(array $fieldStatus): float\n+    {\n+        if ($fieldStatus === []) {\n+            return 0.5;\n+        }\n+\n+        $compliance = 1.0;\n+        foreach ($fieldStatus as $state) {\n+            if ($state === 'missing') {\n+                $compliance -= 0.25;\n+            } elseif ($state === 'review') {\n+                $compliance -= 0.1;\n+            }\n+        }\n+\n+        return max(0.0, min(1.0, round($compliance, 2)));\n+    }\n+\n     private static function callAiService(string $filePath, string $docType, string $mode = MobileConfig::MODE_OCR): array\n     {\n         $serviceUrl = MobileConfig::aiServiceUrl();","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\">@@ -340,19 +340,38 @@</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">     private static function formatItem(AiExpenseQueue $queue, array $extracted = []): array</div><div class=\"diff-line diff-ctx\">     {</div><div class=\"diff-line diff-add\">+        $warnings = $extracted['validation_warnings'] ?? $extracted['warnings'] ?? [];</div><div class=\"diff-line diff-add\">+        $errors = $extracted['validation_errors'] ?? [];</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-ctx\">         return [</div><div class=\"diff-line diff-ctx\">             'id' =&gt; $queue-&gt;id,</div><div class=\"diff-line diff-ctx\">             'doc_type' =&gt; $queue-&gt;doc_type,</div><div class=\"diff-line diff-add\">+            'document_kind' =&gt; $extracted['document_kind'] ?? self::inferDocumentKind($queue-&gt;doc_type),</div><div class=\"diff-line diff-ctx\">             'supplier_name' =&gt; $queue-&gt;supplier_name,</div><div class=\"diff-line diff-ctx\">             'supplier_cif' =&gt; $queue-&gt;supplier_cif,</div><div class=\"diff-line diff-add\">+            'counterparty_address' =&gt; $extracted['counterparty_address'] ?? '',</div><div class=\"diff-line diff-ctx\">             'invoice_number' =&gt; $queue-&gt;invoice_number,</div><div class=\"diff-line diff-add\">+            'invoice_series' =&gt; $extracted['invoice_series'] ?? '',</div><div class=\"diff-line diff-ctx\">             'invoice_date' =&gt; $queue-&gt;invoice_date,</div><div class=\"diff-line diff-add\">+            'operation_date' =&gt; $extracted['operation_date'] ?? $queue-&gt;invoice_date,</div><div class=\"diff-line diff-add\">+            'concept' =&gt; $extracted['concept'] ?? '',</div><div class=\"diff-line diff-ctx\">             'base_amount' =&gt; $queue-&gt;base_amount,</div><div class=\"diff-line diff-add\">+            'tax_rate' =&gt; $extracted['tax_rate'] ?? 21,</div><div class=\"diff-line diff-ctx\">             'tax_amount' =&gt; $queue-&gt;tax_amount,</div><div class=\"diff-line diff-add\">+            'irpf_rate' =&gt; $extracted['irpf_rate'] ?? null,</div><div class=\"diff-line diff-add\">+            'irpf_amount' =&gt; $extracted['irpf_amount'] ?? null,</div><div class=\"diff-line diff-ctx\">             'total_amount' =&gt; $queue-&gt;total_amount,</div><div class=\"diff-line diff-add\">+            'currency' =&gt; $extracted['currency'] ?? 'EUR',</div><div class=\"diff-line diff-add\">+            'payment_method' =&gt; $extracted['payment_method'] ?? '',</div><div class=\"diff-line diff-add\">+            'is_rectifying' =&gt; (bool) ($extracted['is_rectifying'] ?? false),</div><div class=\"diff-line diff-add\">+            'deductible' =&gt; (bool) ($extracted['deductible'] ?? $queue-&gt;doc_type === 'expense'),</div><div class=\"diff-line diff-ctx\">             'confidence' =&gt; $queue-&gt;confidence,</div><div class=\"diff-line diff-add\">+            'fiscal_compliance' =&gt; $extracted['fiscal_compliance'] ?? null,</div><div class=\"diff-line diff-add\">+            'field_status' =&gt; $extracted['field_status'] ?? [],</div><div class=\"diff-line diff-add\">+            'validation_errors' =&gt; $errors,</div><div class=\"diff-line diff-add\">+            'validation_warnings' =&gt; $warnings,</div><div class=\"diff-line diff-ctx\">             'notes' =&gt; $extracted['notes'] ?? '',</div><div class=\"diff-line diff-del\">-            'warnings' =&gt; $extracted['warnings'] ?? [],</div><div class=\"diff-line diff-add\">+            'warnings' =&gt; $warnings,</div><div class=\"diff-line diff-ctx\">             'ocr_engine' =&gt; $extracted['ocr_engine'] ?? '',</div><div class=\"diff-line diff-ctx\">             'image_url' =&gt; $queue-&gt;image_path</div><div class=\"diff-line diff-ctx\">                 ? Tools::config('route') . '/' . $queue-&gt;image_path</div><div class=\"diff-line diff-hunk\">@@ -360,6 +379,108 @@</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\">+    private static function inferDocumentKind(string $docType): string</div><div class=\"diff-line diff-add\">+    {</div><div class=\"diff-line diff-add\">+        return $docType === 'issued' ? 'issued_invoice' : 'expense_received';</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+    /** @param array&lt;string, mixed&gt; $data */</div><div class=\"diff-line diff-add\">+    private static function enrichFiscalValidation(array $data): array</div><div class=\"diff-line diff-add\">+    {</div><div class=\"diff-line diff-add\">+        if (!empty($data['_error'])) {</div><div class=\"diff-line diff-add\">+            return $data;</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        if (empty($data['supplier_name']) &amp;&amp; !empty($data['counterparty_name'])) {</div><div class=\"diff-line diff-add\">+            $data['supplier_name'] = trim((string) $data['counterparty_name']);</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+        if (empty($data['supplier_cif']) &amp;&amp; !empty($data['counterparty_cif'])) {</div><div class=\"diff-line diff-add\">+            $data['supplier_cif'] = trim((string) $data['counterparty_cif']);</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        $cif = strtoupper(trim((string) ($data['supplier_cif'] ?? '')));</div><div class=\"diff-line diff-add\">+        if ($cif !== '') {</div><div class=\"diff-line diff-add\">+            $data['supplier_cif'] = $cif;</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        if (!isset($data['field_status']) || !is_array($data['field_status'])) {</div><div class=\"diff-line diff-add\">+            $data['field_status'] = [];</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        if ($cif !== '') {</div><div class=\"diff-line diff-add\">+            $validator = new FiscalNumberValidator();</div><div class=\"diff-line diff-add\">+            $data['field_status']['counterparty_cif'] = $validator-&gt;isValid($cif) ? 'ok' : 'review';</div><div class=\"diff-line diff-add\">+        } elseif (!isset($data['field_status']['counterparty_cif'])) {</div><div class=\"diff-line diff-add\">+            $data['field_status']['counterparty_cif'] = 'review';</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        if (empty($data['field_status'])) {</div><div class=\"diff-line diff-add\">+            $data['field_status'] = self::computeFieldStatus($data);</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        if (!isset($data['fiscal_compliance'])) {</div><div class=\"diff-line diff-add\">+            $data['fiscal_compliance'] = self::computeCompliance($data['field_status']);</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        $warnings = $data['validation_warnings'] ?? $data['warnings'] ?? [];</div><div class=\"diff-line diff-add\">+        if (!is_array($warnings)) {</div><div class=\"diff-line diff-add\">+            $warnings = [];</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        if ($cif !== '' &amp;&amp; ($data['field_status']['counterparty_cif'] ?? '') === 'review') {</div><div class=\"diff-line diff-add\">+            $warnings[] = 'El NIF/CIF no supera la validación algorítmica española. Revísalo.';</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        $data['validation_warnings'] = array_values(array_unique($warnings));</div><div class=\"diff-line diff-add\">+        $data['warnings'] = $data['validation_warnings'];</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        return $data;</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+    /** @param array&lt;string, mixed&gt; $data */</div><div class=\"diff-line diff-add\">+    private static function computeFieldStatus(array $data): array</div><div class=\"diff-line diff-add\">+    {</div><div class=\"diff-line diff-add\">+        $docKind = $data['document_kind'] ?? 'expense_received';</div><div class=\"diff-line diff-add\">+        $isIssued = $docKind === 'issued_invoice' || ($data['doc_type'] ?? '') === 'issued';</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        $required = $isIssued</div><div class=\"diff-line diff-add\">+            ? ['counterparty_name', 'invoice_number', 'invoice_date', 'total_amount']</div><div class=\"diff-line diff-add\">+            : ['counterparty_name', 'invoice_date', 'total_amount'];</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        $status = [];</div><div class=\"diff-line diff-add\">+        foreach ($required as $field) {</div><div class=\"diff-line diff-add\">+            $value = $data[$field] ?? null;</div><div class=\"diff-line diff-add\">+            if ($field === 'counterparty_name' &amp;&amp; empty($value)) {</div><div class=\"diff-line diff-add\">+                $value = $data['supplier_name'] ?? null;</div><div class=\"diff-line diff-add\">+            }</div><div class=\"diff-line diff-add\">+            if ($field === 'total_amount' &amp;&amp; empty($value)) {</div><div class=\"diff-line diff-add\">+                $value = $data['total_amount'] ?? null;</div><div class=\"diff-line diff-add\">+            }</div><div class=\"diff-line diff-add\">+            $status[$field] = ($value !== null &amp;&amp; $value !== '' &amp;&amp; $value !== 0) ? 'ok' : 'missing';</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        return $status;</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+    /** @param array&lt;string, string&gt; $fieldStatus */</div><div class=\"diff-line diff-add\">+    private static function computeCompliance(array $fieldStatus): float</div><div class=\"diff-line diff-add\">+    {</div><div class=\"diff-line diff-add\">+        if ($fieldStatus === []) {</div><div class=\"diff-line diff-add\">+            return 0.5;</div><div class=\"diff-line diff-add\">+        }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        $compliance = 1.0;</div><div class=\"diff-line diff-add\">+        foreach ($fieldStatus as $state) {</div><div class=\"diff-line diff-add\">+            if ($state === 'missing') {</div><div class=\"diff-line diff-add\">+                $compliance -= 0.25;</div><div class=\"diff-line diff-add\">+            } elseif ($state === 'review') {</div><div class=\"diff-line diff-add\">+                $compliance -= 0.1;</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\">+        return max(0.0, min(1.0, round($compliance, 2)));</div><div class=\"diff-line diff-add\">+    }</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-ctx\">     private static function callAiService(string $filePath, string $docType, string $mode = MobileConfig::MODE_OCR): array</div><div class=\"diff-line diff-ctx\">     {</div><div class=\"diff-line diff-ctx\">         $serviceUrl = MobileConfig::aiServiceUrl();</div></div>","linesAdded":122,"linesRemoved":1,"status":"pending"},"pending":1},"at":1782350807644},{"event":"tool_activity","data":{"activity":{"id":"tool_a434903d-4c74-4b08-bb61-7e457e25cb3","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\AiExpenseProcessor.php","detail":"","at":"2026-06-25T01:26:48.024Z"}},"at":1782350808024},{"event":"file_change","data":{"sessionId":"cs-1782350776480-l80a0v","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@@ -497,6 +497,7 @@\n                 'doc_type' => $docType,\n                 'mode' => $mode,\n                 'ollama_url' => MobileConfig::ollamaUrl(),\n+                'ollama_model' => MobileConfig::ollamaModel(),\n                 'file' => new \\CURLFile(\n                     $filePath,\n                     self::guessMimeType($filePath),","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\">@@ -497,6 +497,7 @@</div><div class=\"diff-line diff-ctx\">                 'doc_type' =&gt; $docType,</div><div class=\"diff-line diff-ctx\">                 'mode' =&gt; $mode,</div><div class=\"diff-line diff-ctx\">                 'ollama_url' =&gt; MobileConfig::ollamaUrl(),</div><div class=\"diff-line diff-add\">+                'ollama_model' =&gt; MobileConfig::ollamaModel(),</div><div class=\"diff-line diff-ctx\">                 'file' =&gt; new \\CURLFile(</div><div class=\"diff-line diff-ctx\">                     $filePath,</div><div class=\"diff-line diff-ctx\">                     self::guessMimeType($filePath),</div></div>","linesAdded":1,"linesRemoved":0,"status":"pending"},"pending":2},"at":1782350808024},{"event":"tool_activity","data":{"activity":{"id":"tool_b1eb7670-236d-45a1-8a6f-94c0ee95ac3","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py","detail":"","at":"2026-06-25T01:26:55.073Z"}},"at":1782350815073},{"event":"file_change","data":{"sessionId":"cs-1782350776480-l80a0v","change":{"id":"ch-3","path":"ai-service\\main.py","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py\n@@ -661,15 +661,19 @@\n     result = _extract_from_text(text, doc_type)\n     result[\"ocr_engine\"] = engine\n \n+    llm_url = (ollama_url or DEFAULT_OLLAMA_URL).strip()\n+    llm_model = (ollama_model or DEFAULT_OLLAMA_MODEL).strip()\n+    llm_data = _ollama_extract(text, doc_type, llm_url, llm_model)\n+\n     if mode == \"ai\":\n-        llm_url = (ollama_url or DEFAULT_OLLAMA_URL).strip()\n-        llm_model = (ollama_model or DEFAULT_OLLAMA_MODEL).strip()\n-        llm_data = _ollama_extract(text, doc_type, llm_url, llm_model)\n         result = _merge_extractions(result, llm_data)\n         if llm_data is None:\n             result.setdefault(\"validation_warnings\", []).append(\n                 \"Ollama no respondió; se usaron solo reglas OCR. Comprueba la red LAN (192.168.1.16:11434).\"\n             )\n+    elif llm_data is not None:\n+        result = _merge_extractions(result, llm_data)\n+        result[\"ocr_engine\"] = (result.get(\"ocr_engine\") or engine) + \"+ollama\"\n \n     result = _validate_fiscal_data(result)\n     result[\"source_file\"] = filename","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py</div><div class=\"diff-line diff-hunk\">@@ -661,15 +661,19 @@</div><div class=\"diff-line diff-ctx\">     result = _extract_from_text(text, doc_type)</div><div class=\"diff-line diff-ctx\">     result[\"ocr_engine\"] = engine</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-add\">+    llm_url = (ollama_url or DEFAULT_OLLAMA_URL).strip()</div><div class=\"diff-line diff-add\">+    llm_model = (ollama_model or DEFAULT_OLLAMA_MODEL).strip()</div><div class=\"diff-line diff-add\">+    llm_data = _ollama_extract(text, doc_type, llm_url, llm_model)</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-ctx\">     if mode == \"ai\":</div><div class=\"diff-line diff-del\">-        llm_url = (ollama_url or DEFAULT_OLLAMA_URL).strip()</div><div class=\"diff-line diff-del\">-        llm_model = (ollama_model or DEFAULT_OLLAMA_MODEL).strip()</div><div class=\"diff-line diff-del\">-        llm_data = _ollama_extract(text, doc_type, llm_url, llm_model)</div><div class=\"diff-line diff-ctx\">         result = _merge_extractions(result, llm_data)</div><div class=\"diff-line diff-ctx\">         if llm_data is None:</div><div class=\"diff-line diff-ctx\">             result.setdefault(\"validation_warnings\", []).append(</div><div class=\"diff-line diff-ctx\">                 \"Ollama no respondió; se usaron solo reglas OCR. Comprueba la red LAN (192.168.1.16:11434).\"</div><div class=\"diff-line diff-ctx\">             )</div><div class=\"diff-line diff-add\">+    elif llm_data is not None:</div><div class=\"diff-line diff-add\">+        result = _merge_extractions(result, llm_data)</div><div class=\"diff-line diff-add\">+        result[\"ocr_engine\"] = (result.get(\"ocr_engine\") or engine) + \"+ollama\"</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">     result = _validate_fiscal_data(result)</div><div class=\"diff-line diff-ctx\">     result[\"source_file\"] = filename</div></div>","linesAdded":7,"linesRemoved":3,"status":"pending"},"pending":3},"at":1782350815074},{"event":"tool_activity","data":{"activity":{"id":"tool_8109de43-b49c-4d43-8265-b76dc76a89a","name":"edit","status":"completed","summary":"edit: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:26:55.776Z"}},"at":1782350815776},{"event":"file_change","data":{"sessionId":"cs-1782350776480-l80a0v","change":{"id":"ch-4","path":"Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n@@ -23,6 +23,9 @@\n         $baseAmount = self::toFloat($data['base_amount'] ?? $queue->base_amount);\n         $taxAmount = self::toFloat($data['tax_amount'] ?? $queue->tax_amount);\n         $totalAmount = self::toFloat($data['total_amount'] ?? $queue->total_amount);\n+        $taxRate = self::toFloat($data['tax_rate'] ?? 21);\n+        $irpfRate = self::toFloat($data['irpf_rate'] ?? 0);\n+        $concept = trim($data['concept'] ?? 'Gasto: ' . $supplierName);\n \n         if ($supplierName === '') {\n             return ['ok' => false, 'message' => 'Indica el nombre del proveedor'];","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php</div><div class=\"diff-line diff-hunk\">@@ -23,6 +23,9 @@</div><div class=\"diff-line diff-ctx\">         $baseAmount = self::toFloat($data['base_amount'] ?? $queue-&gt;base_amount);</div><div class=\"diff-line diff-ctx\">         $taxAmount = self::toFloat($data['tax_amount'] ?? $queue-&gt;tax_amount);</div><div class=\"diff-line diff-ctx\">         $totalAmount = self::toFloat($data['total_amount'] ?? $queue-&gt;total_amount);</div><div class=\"diff-line diff-add\">+        $taxRate = self::toFloat($data['tax_rate'] ?? 21);</div><div class=\"diff-line diff-add\">+        $irpfRate = self::toFloat($data['irpf_rate'] ?? 0);</div><div class=\"diff-line diff-add\">+        $concept = trim($data['concept'] ?? 'Gasto: ' . $supplierName);</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">         if ($supplierName === '') {</div><div class=\"diff-line diff-ctx\">             return ['ok' =&gt; false, 'message' =&gt; 'Indica el nombre del proveedor'];</div></div>","linesAdded":3,"linesRemoved":0,"status":"pending"},"pending":4},"at":1782350815776},{"event":"tool_activity","data":{"activity":{"id":"tool_11530fda-8770-4379-a01a-7b59a458f8a","name":"edit","status":"completed","summary":"edit: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:26:56.677Z"}},"at":1782350816677},{"event":"file_change","data":{"sessionId":"cs-1782350776480-l80a0v","change":{"id":"ch-5","path":"Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n@@ -39,11 +39,12 @@\n             if ($taxAmount > 0) {\n                 $baseAmount = round($totalAmount - $taxAmount, 2);\n             } else {\n-                $baseAmount = round($totalAmount / 1.21, 2);\n+                $rateFactor = 1 + ($taxRate / 100);\n+                $baseAmount = round($totalAmount / $rateFactor, 2);\n                 $taxAmount = round($totalAmount - $baseAmount, 2);\n             }\n         } elseif ($taxAmount <= 0 && $baseAmount > 0 && $totalAmount <= 0) {\n-            $taxAmount = round($baseAmount * 0.21, 2);\n+            $taxAmount = round($baseAmount * ($taxRate / 100), 2);\n             $totalAmount = round($baseAmount + $taxAmount, 2);\n         }\n ","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php</div><div class=\"diff-line diff-hunk\">@@ -39,11 +39,12 @@</div><div class=\"diff-line diff-ctx\">             if ($taxAmount &gt; 0) {</div><div class=\"diff-line diff-ctx\">                 $baseAmount = round($totalAmount - $taxAmount, 2);</div><div class=\"diff-line diff-ctx\">             } else {</div><div class=\"diff-line diff-del\">-                $baseAmount = round($totalAmount / 1.21, 2);</div><div class=\"diff-line diff-add\">+                $rateFactor = 1 + ($taxRate / 100);</div><div class=\"diff-line diff-add\">+                $baseAmount = round($totalAmount / $rateFactor, 2);</div><div class=\"diff-line diff-ctx\">                 $taxAmount = round($totalAmount - $baseAmount, 2);</div><div class=\"diff-line diff-ctx\">             }</div><div class=\"diff-line diff-ctx\">         } elseif ($taxAmount &lt;= 0 &amp;&amp; $baseAmount &gt; 0 &amp;&amp; $totalAmount &lt;= 0) {</div><div class=\"diff-line diff-del\">-            $taxAmount = round($baseAmount * 0.21, 2);</div><div class=\"diff-line diff-add\">+            $taxAmount = round($baseAmount * ($taxRate / 100), 2);</div><div class=\"diff-line diff-ctx\">             $totalAmount = round($baseAmount + $taxAmount, 2);</div><div class=\"diff-line diff-ctx\">         }</div><div class=\"diff-line diff-ctx\"> </div></div>","linesAdded":3,"linesRemoved":2,"status":"pending"},"pending":5},"at":1782350816677},{"event":"tool_activity","data":{"activity":{"id":"tool_cab6aa97-ad92-4ed4-85b7-97bea0fb4e8","name":"edit","status":"completed","summary":"edit: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:26:57.267Z"}},"at":1782350817267},{"event":"file_change","data":{"sessionId":"cs-1782350776480-l80a0v","change":{"id":"ch-6","path":"Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n@@ -64,13 +64,13 @@\n         }\n \n         $line = $invoice->getNewLine();\n-        $line->descripcion = 'Gasto: ' . $supplierName;\n+        $line->descripcion = $concept;\n         $line->cantidad = 1;\n         $line->pvpunitario = $baseAmount;\n-        $line->codimpuesto = self::defaultTaxCode();\n+        $line->codimpuesto = self::taxCodeFromRate($taxRate);\n         $line->dtopor = 0;\n         $line->dtopor2 = 0;\n-        $line->irpf = 0;\n+        $line->irpf = $irpfRate > 0 ? $irpfRate : 0;\n \n         if (!$line->save()) {\n             return ['ok' => false, 'message' => 'No se pudo crear la línea de factura'];","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php</div><div class=\"diff-line diff-hunk\">@@ -64,13 +64,13 @@</div><div class=\"diff-line diff-ctx\">         }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">         $line = $invoice-&gt;getNewLine();</div><div class=\"diff-line diff-del\">-        $line-&gt;descripcion = 'Gasto: ' . $supplierName;</div><div class=\"diff-line diff-add\">+        $line-&gt;descripcion = $concept;</div><div class=\"diff-line diff-ctx\">         $line-&gt;cantidad = 1;</div><div class=\"diff-line diff-ctx\">         $line-&gt;pvpunitario = $baseAmount;</div><div class=\"diff-line diff-del\">-        $line-&gt;codimpuesto = self::defaultTaxCode();</div><div class=\"diff-line diff-add\">+        $line-&gt;codimpuesto = self::taxCodeFromRate($taxRate);</div><div class=\"diff-line diff-ctx\">         $line-&gt;dtopor = 0;</div><div class=\"diff-line diff-ctx\">         $line-&gt;dtopor2 = 0;</div><div class=\"diff-line diff-del\">-        $line-&gt;irpf = 0;</div><div class=\"diff-line diff-add\">+        $line-&gt;irpf = $irpfRate &gt; 0 ? $irpfRate : 0;</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">         if (!$line-&gt;save()) {</div><div class=\"diff-line diff-ctx\">             return ['ok' =&gt; false, 'message' =&gt; 'No se pudo crear la línea de factura'];</div></div>","linesAdded":3,"linesRemoved":3,"status":"pending"},"pending":6},"at":1782350817267},{"event":"tool_activity","data":{"activity":{"id":"tool_19a2c6f2-c04f-49c3-84d0-5bf6bd7736f","name":"edit","status":"completed","summary":"edit: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","detail":"","at":"2026-06-25T01:26:57.639Z"}},"at":1782350817639},{"event":"file_change","data":{"sessionId":"cs-1782350776480-l80a0v","change":{"id":"ch-7","path":"Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n@@ -145,6 +145,18 @@\n         }\n     }\n \n+    private static function taxCodeFromRate(float $rate): string\n+    {\n+        $rate = (int) round($rate);\n+\n+        return match ($rate) {\n+            10 => 'IVA10',\n+            4 => 'IVA4',\n+            0 => 'IVA0',\n+            default => 'IVA21',\n+        };\n+    }\n+\n     private static function defaultTaxCode(): string\n     {\n         return 'IVA21';","diffHtml":"<div class=\"diff-colored\"><div class=\"diff-line diff-meta\">--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php</div><div class=\"diff-line diff-meta\">+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php</div><div class=\"diff-line diff-hunk\">@@ -145,6 +145,18 @@</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\">+    private static function taxCodeFromRate(float $rate): string</div><div class=\"diff-line diff-add\">+    {</div><div class=\"diff-line diff-add\">+        $rate = (int) round($rate);</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+        return match ($rate) {</div><div class=\"diff-line diff-add\">+            10 =&gt; 'IVA10',</div><div class=\"diff-line diff-add\">+            4 =&gt; 'IVA4',</div><div class=\"diff-line diff-add\">+            0 =&gt; 'IVA0',</div><div class=\"diff-line diff-add\">+            default =&gt; 'IVA21',</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\">     private static function defaultTaxCode(): string</div><div class=\"diff-line diff-ctx\">     {</div><div class=\"diff-line diff-ctx\">         return 'IVA21';</div></div>","linesAdded":12,"linesRemoved":0,"status":"pending"},"pending":7},"at":1782350817640},{"event":"text","data":{"chunk":"Actualizando la UI de confirmación fiscal en Twig, CSS y JS.\n"},"at":1782350833096},{"event":"tool_activity","data":{"activity":{"id":"tool_aefe9c8b-7b3f-4fe5-addb-75e20cc7d14","name":"edit","status":"completed","summary":"edit: …ampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\View\\MobileExpenses.html.twig","detail":"","at":"2026-06-25T01:27:10.633Z"}},"at":1782350830633},{"event":"file_change","data":{"sessionId":"cs-1782350776480-l80a0v","change":{"id":"ch-8","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@@ -96,54 +96,148 @@\n     </div>\n \n     <div class=\"modal fade\" id=\"confirmModal\" tabindex=\"-1\" aria-labelledby=\"confirmModalLabel\">\n-        <div class=\"modal-dialog modal-dialog-centered modal-fullscreen-sm-down\">\n-            <div class=\"modal-content\" data-guide=\"expense-confirm\">\n-                <div class=\"modal-header\">\n-                    <h5 class=\"modal-title\" id=\"confirmModalLabel\">Confirmar datos</h5>\n-                    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"></button>\n-                </div>\n-                <div class=\"modal-body\">\n-                    <div id=\"confirm-preview-image\" class=\"mb-3 text-center\"></div>\n-                    <form id=\"confirm-form\">\n-                        <input type=\"hidden\" name=\"queue_id\" id=\"confirm-queue-id\">\n-                        <div class=\"mb-2\">\n-                            <label class=\"form-label\">Proveedor / emisor</label>\n-                            <input type=\"text\" class=\"form-control\" name=\"supplier_name\" id=\"cf-supplier-name\" required>\n+        <div class=\"modal-dialog modal-dialog-centered modal-fullscreen-sm-down modal-dialog-scrollable\">\n+            <div class=\"modal-content fiscal-confirm-modal\" data-guide=\"expense-confirm\">\n+                <div class=\"modal-header border-0 pb-0\">\n+                    <div class=\"w-100\">\n+                        <div class=\"d-flex align-items-center justify-content-between gap-2 mb-2\">\n+                            <h5 class=\"modal-title mb-0\" id=\"confirmModalLabel\">Revisión fiscal</h5>\n+                            <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Cerrar\"></button>\n                         </div>\n-                        <div class=\"mb-2\">\n-                            <label class=\"form-label\">NIF/CIF</label>\n-                            <input type=\"text\" class=\"form-control\" name=\"supplier_cif\" id=\"cf-supplier-cif\">\n+                        <div class=\"fiscal-badges d-flex flex-wrap gap-2\">\n+                            <span class=\"badge fiscal-badge-doc\" id=\"cf-doc-kind-badge\">Factura recibida</span>\n+                            <span class=\"badge fiscal-badge-engine\" id=\"cf-engine-badge\"></span>\n                         </div>\n-                        <div class=\"mb-2\">\n-                            <label class=\"form-label\">Nº factura</label>\n-                            <input type=\"text\" class=\"form-control\" name=\"invoice_number\" id=\"cf-invoice-number\">\n+                    </div>\n+                </div>\n+                <div class=\"modal-body pt-2\">\n+                    <div id=\"confirm-preview-image\" class=\"fiscal-preview mb-3\"></div>\n+\n+                    <div class=\"fiscal-compliance-card mb-3\" id=\"cf-compliance-wrap\">\n+                        <div class=\"d-flex justify-content-between align-items-center mb-1\">\n+                            <span class=\"small fw-semibold\">Cumplimiento fiscal</span>\n+                            <span class=\"small\" id=\"cf-compliance-pct\">—</span>\n                         </div>\n-                        <div class=\"mb-2\">\n-                            <label class=\"form-label\">Fecha</label>\n-                            <input type=\"date\" class=\"form-control\" name=\"invoice_date\" id=\"cf-invoice-date\">\n+                        <div class=\"fiscal-compliance-bar\">\n+                            <div class=\"fiscal-compliance-fill\" id=\"cf-compliance-fill\"></div>\n                         </div>\n-                        <div class=\"row g-2\">\n-                            <div class=\"col-4\">\n-                                <label class=\"form-label\">Base €</label>\n-                                <input type=\"number\" step=\"0.01\" class=\"form-control\" name=\"base_amount\" id=\"cf-base\">\n+                        <div class=\"small text-muted mt-1\" id=\"cf-confidence-text\"></div>\n+                    </div>\n+\n+                    <div id=\"cf-alerts\" class=\"mb-3\"></div>\n+\n+                    <form id=\"confirm-form\">\n+                        <input type=\"hidden\" name=\"queue_id\" id=\"confirm-queue-id\">\n+                        <input type=\"hidden\" name=\"doc_type\" id=\"cf-doc-type\" value=\"expense\">\n+\n+                        <section class=\"fiscal-section\">\n+                            <h6 class=\"fiscal-section-title\">Identificación (RD 1619/2012)</h6>\n+                            <div class=\"mb-2 fiscal-field\" data-field=\"counterparty_name\">\n+                                <label class=\"form-label\">Emisor / proveedor</label>\n+                                <input type=\"text\" class=\"form-control\" name=\"supplier_name\" id=\"cf-supplier-name\" required>\n                             </div>\n-                            <div class=\"col-4\">\n-                                <label class=\"form-label\">IVA €</label>\n-                                <input type=\"number\" step=\"0.01\" class=\"form-control\" name=\"tax_amount\" id=\"cf-tax\">\n+                            <div class=\"mb-2 fiscal-field\" data-field=\"counterparty_cif\">\n+                                <label class=\"form-label\">NIF / CIF emisor</label>\n+                                <input type=\"text\" class=\"form-control\" name=\"supplier_cif\" id=\"cf-supplier-cif\" placeholder=\"B12345678\">\n                             </div>\n-                            <div class=\"col-4\">\n-                                <label class=\"form-label\">Total €</label>\n-                                <input type=\"number\" step=\"0.01\" class=\"form-control\" name=\"total_amount\" id=\"cf-total\">\n+                            <div class=\"mb-2 fiscal-field\" data-field=\"counterparty_address\">\n+                                <label class=\"form-label\">Domicilio emisor</label>\n+                                <input type=\"text\" class=\"form-control\" name=\"counterparty_address\" id=\"cf-address\" placeholder=\"Opcional en ticket simplificado\">\n                             </div>\n-                        </div>\n-                        <div class=\"alert alert-info mt-3 small mb-0\" id=\"cf-notes\"></div>\n+                        </section>\n+\n+                        <section class=\"fiscal-section\">\n+                            <h6 class=\"fiscal-section-title\">Datos del documento</h6>\n+                            <div class=\"row g-2 mb-2\">\n+                                <div class=\"col-6 fiscal-field\" data-field=\"invoice_series\">\n+                                    <label class=\"form-label\">Serie</label>\n+                                    <input type=\"text\" class=\"form-control\" name=\"invoice_series\" id=\"cf-invoice-series\">\n+                                </div>\n+                                <div class=\"col-6 fiscal-field\" data-field=\"invoice_number\">\n+                                    <label class=\"form-label\">Nº factura</label>\n+                                    <input type=\"text\" class=\"form-control\" name=\"invoice_number\" id=\"cf-invoice-number\">\n+                                </div>\n+                            </div>\n+                            <div class=\"row g-2 mb-2\">\n+                                <div class=\"col-6 fiscal-field\" data-field=\"invoice_date\">\n+                                    <label class=\"form-label\">Fecha factura</label>\n+                                    <input type=\"date\" class=\"form-control\" name=\"invoice_date\" id=\"cf-invoice-date\">\n+                                </div>\n+                                <div class=\"col-6 fiscal-field\" data-field=\"operation_date\">\n+                                    <label class=\"form-label\">Fecha operación</label>\n+                                    <input type=\"date\" class=\"form-control\" name=\"operation_date\" id=\"cf-operation-date\">\n+                                </div>\n+                            </div>\n+                            <div class=\"mb-2 fiscal-field\" data-field=\"concept\">\n+                                <label class=\"form-label\">Concepto / descripción</label>\n+                                <input type=\"text\" class=\"form-control\" name=\"concept\" id=\"cf-concept\" placeholder=\"Descripción del gasto o servicio\">\n+                            </div>\n+                        </section>\n+\n+                        <section class=\"fiscal-section\">\n+                            <h6 class=\"fiscal-section-title\">Importes (Ley IVA)</h6>\n+                            <div class=\"row g-2 mb-2\">\n+                                <div class=\"col-6 fiscal-field\" data-field=\"base_amount\">\n+                                    <label class=\"form-label\">Base imponible €</label>\n+                                    <input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount\" name=\"base_amount\" id=\"cf-base\">\n+                                </div>\n+                                <div class=\"col-6 fiscal-field\" data-field=\"tax_rate\">\n+                                    <label class=\"form-label\">Tipo IVA</label>\n+                                    <select class=\"form-select fiscal-amount\" name=\"tax_rate\" id=\"cf-tax-rate\">\n+                                        <option value=\"21\">21 % (general)</option>\n+                                        <option value=\"10\">10 % (reducido)</option>\n+                                        <option value=\"4\">4 % (superreducido)</option>\n+                                        <option value=\"0\">0 % (exento)</option>\n+                                    </select>\n+                                </div>\n+                            </div>\n+                            <div class=\"row g-2 mb-2\">\n+                                <div class=\"col-4 fiscal-field\" data-field=\"tax_amount\">\n+                                    <label class=\"form-label\">Cuota IVA €</label>\n+                                    <input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount\" name=\"tax_amount\" id=\"cf-tax\">\n+                                </div>\n+                                <div class=\"col-4 fiscal-field\" data-field=\"irpf_amount\">\n+                                    <label class=\"form-label\">IRPF €</label>\n+                                    <input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount\" name=\"irpf_amount\" id=\"cf-irpf-amount\">\n+                                </div>\n+                                <div class=\"col-4 fiscal-field\" data-field=\"total_amount\">\n+                                    <label class=\"form-label\">Total €</label>\n+                                    <input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount fw-bold\" name=\"total_amount\" id=\"cf-total\" required>\n+                                </div>\n+                            </div>\n+                            <div class=\"mb-2 fiscal-field\" data-field=\"irpf_rate\">\n+                                <label class=\"form-label\">Retención IRPF %</label>\n+                                <select class=\"form-select\" name=\"irpf_rate\" id=\"cf-irpf-rate\">\n+                                    <option value=\"0\">Sin retención</option>\n+                                    <option value=\"7\">7 %</option>\n+                                    <option value=\"15\">15 %</option>\n+                                    <option value=\"19\">19 %</option>\n+                                </select>\n+                            </div>\n+                        </section>\n+\n+                        <section class=\"fiscal-section\">\n+                            <h6 class=\"fiscal-section-title\">Clasificación tributaria</h6>\n+                            <div class=\"d-flex flex-wrap gap-3\">\n+                                <div class=\"form-check\">\n+                                    <input class=\"form-check-input\" type=\"checkbox\" name=\"deductible\" id=\"cf-deductible\" value=\"1\">\n+                                    <label class=\"form-check-label\" for=\"cf-deductible\">Gasto deducible</label>\n+                                </div>\n+                                <div class=\"form-check\">\n+                                    <input class=\"form-check-input\" type=\"checkbox\" name=\"is_rectifying\" id=\"cf-rectifying\" value=\"1\">\n+                                    <label class=\"form-check-label\" for=\"cf-rectifying\">Factura rectificativa</label>\n+                                </div>\n+                            </div>\n+                        </section>\n+\n+                        <div class=\"alert alert-light border mt-3 small mb-0\" id=\"cf-notes\"></div>\n                     </form>\n                 </div>\n-                <div class=\"modal-footer d-grid gap-2\">\n+                <div class=\"modal-footer border-0 d-grid gap-2 pt-0\">\n                     <button type=\"button\" class=\"btn btn-success btn-lg\" id=\"btn-confirm-ok\">\n-                        <i class=\"fa-solid fa-check me-2\"></i>Confirmar y crear factura\n+                        <i class=\"fa-solid fa-check me-2\"></i>Confirmar y registrar\n                     </button>\n-                    <button type=\"button\" class=\"btn btn-outline-danger\" id=\"btn-confirm-reject\">Rechazar</button>\n+                    <button type=\"button\" class=\"btn btn-outline-danger\" id=\"btn-confirm-reject\">Rechazar documento</button>\n                 </div>\n             </div>\n         </div>","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\">@@ -96,54 +96,148 @@</div><div class=\"diff-line diff-ctx\">     &lt;/div&gt;</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-ctx\">     &lt;div class=\"modal fade\" id=\"confirmModal\" tabindex=\"-1\" aria-labelledby=\"confirmModalLabel\"&gt;</div><div class=\"diff-line diff-del\">-        &lt;div class=\"modal-dialog modal-dialog-centered modal-fullscreen-sm-down\"&gt;</div><div class=\"diff-line diff-del\">-            &lt;div class=\"modal-content\" data-guide=\"expense-confirm\"&gt;</div><div class=\"diff-line diff-del\">-                &lt;div class=\"modal-header\"&gt;</div><div class=\"diff-line diff-del\">-                    &lt;h5 class=\"modal-title\" id=\"confirmModalLabel\"&gt;Confirmar datos&lt;/h5&gt;</div><div class=\"diff-line diff-del\">-                    &lt;button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"&gt;&lt;/button&gt;</div><div class=\"diff-line diff-del\">-                &lt;/div&gt;</div><div class=\"diff-line diff-del\">-                &lt;div class=\"modal-body\"&gt;</div><div class=\"diff-line diff-del\">-                    &lt;div id=\"confirm-preview-image\" class=\"mb-3 text-center\"&gt;&lt;/div&gt;</div><div class=\"diff-line diff-del\">-                    &lt;form id=\"confirm-form\"&gt;</div><div class=\"diff-line diff-del\">-                        &lt;input type=\"hidden\" name=\"queue_id\" id=\"confirm-queue-id\"&gt;</div><div class=\"diff-line diff-del\">-                        &lt;div class=\"mb-2\"&gt;</div><div class=\"diff-line diff-del\">-                            &lt;label class=\"form-label\"&gt;Proveedor / emisor&lt;/label&gt;</div><div class=\"diff-line diff-del\">-                            &lt;input type=\"text\" class=\"form-control\" name=\"supplier_name\" id=\"cf-supplier-name\" required&gt;</div><div class=\"diff-line diff-add\">+        &lt;div class=\"modal-dialog modal-dialog-centered modal-fullscreen-sm-down modal-dialog-scrollable\"&gt;</div><div class=\"diff-line diff-add\">+            &lt;div class=\"modal-content fiscal-confirm-modal\" data-guide=\"expense-confirm\"&gt;</div><div class=\"diff-line diff-add\">+                &lt;div class=\"modal-header border-0 pb-0\"&gt;</div><div class=\"diff-line diff-add\">+                    &lt;div class=\"w-100\"&gt;</div><div class=\"diff-line diff-add\">+                        &lt;div class=\"d-flex align-items-center justify-content-between gap-2 mb-2\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;h5 class=\"modal-title mb-0\" id=\"confirmModalLabel\"&gt;Revisión fiscal&lt;/h5&gt;</div><div class=\"diff-line diff-add\">+                            &lt;button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Cerrar\"&gt;&lt;/button&gt;</div><div class=\"diff-line diff-ctx\">                         &lt;/div&gt;</div><div class=\"diff-line diff-del\">-                        &lt;div class=\"mb-2\"&gt;</div><div class=\"diff-line diff-del\">-                            &lt;label class=\"form-label\"&gt;NIF/CIF&lt;/label&gt;</div><div class=\"diff-line diff-del\">-                            &lt;input type=\"text\" class=\"form-control\" name=\"supplier_cif\" id=\"cf-supplier-cif\"&gt;</div><div class=\"diff-line diff-add\">+                        &lt;div class=\"fiscal-badges d-flex flex-wrap gap-2\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;span class=\"badge fiscal-badge-doc\" id=\"cf-doc-kind-badge\"&gt;Factura recibida&lt;/span&gt;</div><div class=\"diff-line diff-add\">+                            &lt;span class=\"badge fiscal-badge-engine\" id=\"cf-engine-badge\"&gt;&lt;/span&gt;</div><div class=\"diff-line diff-ctx\">                         &lt;/div&gt;</div><div class=\"diff-line diff-del\">-                        &lt;div class=\"mb-2\"&gt;</div><div class=\"diff-line diff-del\">-                            &lt;label class=\"form-label\"&gt;Nº factura&lt;/label&gt;</div><div class=\"diff-line diff-del\">-                            &lt;input type=\"text\" class=\"form-control\" name=\"invoice_number\" id=\"cf-invoice-number\"&gt;</div><div class=\"diff-line diff-add\">+                    &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                &lt;div class=\"modal-body pt-2\"&gt;</div><div class=\"diff-line diff-add\">+                    &lt;div id=\"confirm-preview-image\" class=\"fiscal-preview mb-3\"&gt;&lt;/div&gt;</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+                    &lt;div class=\"fiscal-compliance-card mb-3\" id=\"cf-compliance-wrap\"&gt;</div><div class=\"diff-line diff-add\">+                        &lt;div class=\"d-flex justify-content-between align-items-center mb-1\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;span class=\"small fw-semibold\"&gt;Cumplimiento fiscal&lt;/span&gt;</div><div class=\"diff-line diff-add\">+                            &lt;span class=\"small\" id=\"cf-compliance-pct\"&gt;—&lt;/span&gt;</div><div class=\"diff-line diff-ctx\">                         &lt;/div&gt;</div><div class=\"diff-line diff-del\">-                        &lt;div class=\"mb-2\"&gt;</div><div class=\"diff-line diff-del\">-                            &lt;label class=\"form-label\"&gt;Fecha&lt;/label&gt;</div><div class=\"diff-line diff-del\">-                            &lt;input type=\"date\" class=\"form-control\" name=\"invoice_date\" id=\"cf-invoice-date\"&gt;</div><div class=\"diff-line diff-add\">+                        &lt;div class=\"fiscal-compliance-bar\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"fiscal-compliance-fill\" id=\"cf-compliance-fill\"&gt;&lt;/div&gt;</div><div class=\"diff-line diff-ctx\">                         &lt;/div&gt;</div><div class=\"diff-line diff-del\">-                        &lt;div class=\"row g-2\"&gt;</div><div class=\"diff-line diff-del\">-                            &lt;div class=\"col-4\"&gt;</div><div class=\"diff-line diff-del\">-                                &lt;label class=\"form-label\"&gt;Base €&lt;/label&gt;</div><div class=\"diff-line diff-del\">-                                &lt;input type=\"number\" step=\"0.01\" class=\"form-control\" name=\"base_amount\" id=\"cf-base\"&gt;</div><div class=\"diff-line diff-add\">+                        &lt;div class=\"small text-muted mt-1\" id=\"cf-confidence-text\"&gt;&lt;/div&gt;</div><div class=\"diff-line diff-add\">+                    &lt;/div&gt;</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+                    &lt;div id=\"cf-alerts\" class=\"mb-3\"&gt;&lt;/div&gt;</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+                    &lt;form id=\"confirm-form\"&gt;</div><div class=\"diff-line diff-add\">+                        &lt;input type=\"hidden\" name=\"queue_id\" id=\"confirm-queue-id\"&gt;</div><div class=\"diff-line diff-add\">+                        &lt;input type=\"hidden\" name=\"doc_type\" id=\"cf-doc-type\" value=\"expense\"&gt;</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+                        &lt;section class=\"fiscal-section\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;h6 class=\"fiscal-section-title\"&gt;Identificación (RD 1619/2012)&lt;/h6&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"mb-2 fiscal-field\" data-field=\"counterparty_name\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;label class=\"form-label\"&gt;Emisor / proveedor&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                &lt;input type=\"text\" class=\"form-control\" name=\"supplier_name\" id=\"cf-supplier-name\" required&gt;</div><div class=\"diff-line diff-ctx\">                             &lt;/div&gt;</div><div class=\"diff-line diff-del\">-                            &lt;div class=\"col-4\"&gt;</div><div class=\"diff-line diff-del\">-                                &lt;label class=\"form-label\"&gt;IVA €&lt;/label&gt;</div><div class=\"diff-line diff-del\">-                                &lt;input type=\"number\" step=\"0.01\" class=\"form-control\" name=\"tax_amount\" id=\"cf-tax\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"mb-2 fiscal-field\" data-field=\"counterparty_cif\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;label class=\"form-label\"&gt;NIF / CIF emisor&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                &lt;input type=\"text\" class=\"form-control\" name=\"supplier_cif\" id=\"cf-supplier-cif\" placeholder=\"B12345678\"&gt;</div><div class=\"diff-line diff-ctx\">                             &lt;/div&gt;</div><div class=\"diff-line diff-del\">-                            &lt;div class=\"col-4\"&gt;</div><div class=\"diff-line diff-del\">-                                &lt;label class=\"form-label\"&gt;Total €&lt;/label&gt;</div><div class=\"diff-line diff-del\">-                                &lt;input type=\"number\" step=\"0.01\" class=\"form-control\" name=\"total_amount\" id=\"cf-total\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"mb-2 fiscal-field\" data-field=\"counterparty_address\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;label class=\"form-label\"&gt;Domicilio emisor&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                &lt;input type=\"text\" class=\"form-control\" name=\"counterparty_address\" id=\"cf-address\" placeholder=\"Opcional en ticket simplificado\"&gt;</div><div class=\"diff-line diff-ctx\">                             &lt;/div&gt;</div><div class=\"diff-line diff-del\">-                        &lt;/div&gt;</div><div class=\"diff-line diff-del\">-                        &lt;div class=\"alert alert-info mt-3 small mb-0\" id=\"cf-notes\"&gt;&lt;/div&gt;</div><div class=\"diff-line diff-add\">+                        &lt;/section&gt;</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+                        &lt;section class=\"fiscal-section\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;h6 class=\"fiscal-section-title\"&gt;Datos del documento&lt;/h6&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"row g-2 mb-2\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"col-6 fiscal-field\" data-field=\"invoice_series\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-label\"&gt;Serie&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;input type=\"text\" class=\"form-control\" name=\"invoice_series\" id=\"cf-invoice-series\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"col-6 fiscal-field\" data-field=\"invoice_number\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-label\"&gt;Nº factura&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;input type=\"text\" class=\"form-control\" name=\"invoice_number\" id=\"cf-invoice-number\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                            &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"row g-2 mb-2\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"col-6 fiscal-field\" data-field=\"invoice_date\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-label\"&gt;Fecha factura&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;input type=\"date\" class=\"form-control\" name=\"invoice_date\" id=\"cf-invoice-date\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"col-6 fiscal-field\" data-field=\"operation_date\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-label\"&gt;Fecha operación&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;input type=\"date\" class=\"form-control\" name=\"operation_date\" id=\"cf-operation-date\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                            &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"mb-2 fiscal-field\" data-field=\"concept\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;label class=\"form-label\"&gt;Concepto / descripción&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                &lt;input type=\"text\" class=\"form-control\" name=\"concept\" id=\"cf-concept\" placeholder=\"Descripción del gasto o servicio\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                        &lt;/section&gt;</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+                        &lt;section class=\"fiscal-section\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;h6 class=\"fiscal-section-title\"&gt;Importes (Ley IVA)&lt;/h6&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"row g-2 mb-2\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"col-6 fiscal-field\" data-field=\"base_amount\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-label\"&gt;Base imponible €&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount\" name=\"base_amount\" id=\"cf-base\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"col-6 fiscal-field\" data-field=\"tax_rate\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-label\"&gt;Tipo IVA&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;select class=\"form-select fiscal-amount\" name=\"tax_rate\" id=\"cf-tax-rate\"&gt;</div><div class=\"diff-line diff-add\">+                                        &lt;option value=\"21\"&gt;21 % (general)&lt;/option&gt;</div><div class=\"diff-line diff-add\">+                                        &lt;option value=\"10\"&gt;10 % (reducido)&lt;/option&gt;</div><div class=\"diff-line diff-add\">+                                        &lt;option value=\"4\"&gt;4 % (superreducido)&lt;/option&gt;</div><div class=\"diff-line diff-add\">+                                        &lt;option value=\"0\"&gt;0 % (exento)&lt;/option&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;/select&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                            &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"row g-2 mb-2\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"col-4 fiscal-field\" data-field=\"tax_amount\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-label\"&gt;Cuota IVA €&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount\" name=\"tax_amount\" id=\"cf-tax\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"col-4 fiscal-field\" data-field=\"irpf_amount\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-label\"&gt;IRPF €&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount\" name=\"irpf_amount\" id=\"cf-irpf-amount\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"col-4 fiscal-field\" data-field=\"total_amount\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-label\"&gt;Total €&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount fw-bold\" name=\"total_amount\" id=\"cf-total\" required&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                            &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"mb-2 fiscal-field\" data-field=\"irpf_rate\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;label class=\"form-label\"&gt;Retención IRPF %&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                &lt;select class=\"form-select\" name=\"irpf_rate\" id=\"cf-irpf-rate\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;option value=\"0\"&gt;Sin retención&lt;/option&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;option value=\"7\"&gt;7 %&lt;/option&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;option value=\"15\"&gt;15 %&lt;/option&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;option value=\"19\"&gt;19 %&lt;/option&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/select&gt;</div><div class=\"diff-line diff-add\">+                            &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                        &lt;/section&gt;</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+                        &lt;section class=\"fiscal-section\"&gt;</div><div class=\"diff-line diff-add\">+                            &lt;h6 class=\"fiscal-section-title\"&gt;Clasificación tributaria&lt;/h6&gt;</div><div class=\"diff-line diff-add\">+                            &lt;div class=\"d-flex flex-wrap gap-3\"&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"form-check\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;input class=\"form-check-input\" type=\"checkbox\" name=\"deductible\" id=\"cf-deductible\" value=\"1\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-check-label\" for=\"cf-deductible\"&gt;Gasto deducible&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                                &lt;div class=\"form-check\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;input class=\"form-check-input\" type=\"checkbox\" name=\"is_rectifying\" id=\"cf-rectifying\" value=\"1\"&gt;</div><div class=\"diff-line diff-add\">+                                    &lt;label class=\"form-check-label\" for=\"cf-rectifying\"&gt;Factura rectificativa&lt;/label&gt;</div><div class=\"diff-line diff-add\">+                                &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                            &lt;/div&gt;</div><div class=\"diff-line diff-add\">+                        &lt;/section&gt;</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+                        &lt;div class=\"alert alert-light border mt-3 small mb-0\" id=\"cf-notes\"&gt;&lt;/div&gt;</div><div class=\"diff-line diff-ctx\">                     &lt;/form&gt;</div><div class=\"diff-line diff-ctx\">                 &lt;/div&gt;</div><div class=\"diff-line diff-del\">-                &lt;div class=\"modal-footer d-grid gap-2\"&gt;</div><div class=\"diff-line diff-add\">+                &lt;div class=\"modal-footer border-0 d-grid gap-2 pt-0\"&gt;</div><div class=\"diff-line diff-ctx\">                     &lt;button type=\"button\" class=\"btn btn-success btn-lg\" id=\"btn-confirm-ok\"&gt;</div><div class=\"diff-line diff-del\">-                        &lt;i class=\"fa-solid fa-check me-2\"&gt;&lt;/i&gt;Confirmar y crear factura</div><div class=\"diff-line diff-add\">+                        &lt;i class=\"fa-solid fa-check me-2\"&gt;&lt;/i&gt;Confirmar y registrar</div><div class=\"diff-line diff-ctx\">                     &lt;/button&gt;</div><div class=\"diff-line diff-del\">-                    &lt;button type=\"button\" class=\"btn btn-outline-danger\" id=\"btn-confirm-reject\"&gt;Rechazar&lt;/button&gt;</div><div class=\"diff-line diff-add\">+                    &lt;button type=\"button\" class=\"btn btn-outline-danger\" id=\"btn-confirm-reject\"&gt;Rechazar documento&lt;/button&gt;</div><div class=\"diff-line diff-ctx\">                 &lt;/div&gt;</div><div class=\"diff-line diff-ctx\">             &lt;/div&gt;</div><div class=\"diff-line diff-ctx\">         &lt;/div&gt;</div></div>","linesAdded":131,"linesRemoved":37,"status":"pending"},"pending":8},"at":1782350830634},{"event":"tool_activity","data":{"activity":{"id":"tool_f23db580-c812-4445-bb9d-02a5b533f03","name":"edit","status":"completed","summary":"edit: C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Assets\\CSS\\mobile.css","detail":"","at":"2026-06-25T01:27:13.084Z"}},"at":1782350833084},{"event":"file_change","data":{"sessionId":"cs-1782350776480-l80a0v","change":{"id":"ch-9","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@@ -218,6 +218,119 @@\n     border-radius: 16px;\n }\n \n+.fiscal-confirm-modal {\n+    border-radius: 20px 20px 0 0;\n+    background: #f8f9fb;\n+}\n+\n+.fiscal-confirm-modal .modal-body {\n+    padding-top: 0.5rem;\n+}\n+\n+.fiscal-badges .badge {\n+    font-size: 0.72rem;\n+    padding: 0.35em 0.65em;\n+    border-radius: 8px;\n+}\n+\n+.fiscal-badge-doc {\n+    background: #0d6efd;\n+    color: #fff;\n+}\n+\n+.fiscal-badge-engine {\n+    background: #e9ecef;\n+    color: #495057;\n+}\n+\n+.fiscal-preview img {\n+    max-height: 160px;\n+    border-radius: 12px;\n+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);\n+}\n+\n+.fiscal-compliance-card {\n+    background: #fff;\n+    border-radius: 12px;\n+    padding: 12px 14px;\n+    border: 1px solid #e2e6ea;\n+}\n+\n+.fiscal-compliance-bar {\n+    height: 8px;\n+    background: #e9ecef;\n+    border-radius: 4px;\n+    overflow: hidden;\n+}\n+\n+.fiscal-compliance-fill {\n+    height: 100%;\n+    width: 0;\n+    background: linear-gradient(90deg, #dc3545, #ffc107, #198754);\n+    border-radius: 4px;\n+    transition: width 0.4s ease;\n+}\n+\n+.fiscal-section {\n+    background: #fff;\n+    border-radius: 14px;\n+    padding: 14px;\n+    margin-bottom: 12px;\n+    border: 1px solid #e8eaed;\n+}\n+\n+.fiscal-section-title {\n+    font-size: 0.78rem;\n+    font-weight: 700;\n+    text-transform: uppercase;\n+    letter-spacing: 0.04em;\n+    color: #6c757d;\n+    margin-bottom: 12px;\n+}\n+\n+.fiscal-field .form-label {\n+    font-size: 0.82rem;\n+    margin-bottom: 0.25rem;\n+    color: #495057;\n+}\n+\n+.fiscal-field--ok .form-control,\n+.fiscal-field--ok .form-select {\n+    border-color: #198754;\n+    background-color: rgba(25, 135, 84, 0.04);\n+}\n+\n+.fiscal-field--review .form-control,\n+.fiscal-field--review .form-select {\n+    border-color: #ffc107;\n+    background-color: rgba(255, 193, 7, 0.08);\n+}\n+\n+.fiscal-field--missing .form-control,\n+.fiscal-field--missing .form-select {\n+    border-color: #dc3545;\n+    background-color: rgba(220, 53, 69, 0.06);\n+}\n+\n+.fiscal-alert-item {\n+    border-radius: 10px;\n+    padding: 8px 12px;\n+    margin-bottom: 6px;\n+    font-size: 0.82rem;\n+}\n+\n+.fiscal-alert-error {\n+    background: rgba(220, 53, 69, 0.1);\n+    color: #842029;\n+    border: 1px solid rgba(220, 53, 69, 0.25);\n+}\n+\n+.fiscal-alert-warning {\n+    background: rgba(255, 193, 7, 0.12);\n+    color: #664d03;\n+    border: 1px solid rgba(255, 193, 7, 0.35);\n+}\n+\n @media (min-width: 768px) {\n     .mobile-main {\n         margin: 0 auto;","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\">@@ -218,6 +218,119 @@</div><div class=\"diff-line diff-ctx\">     border-radius: 16px;</div><div class=\"diff-line diff-ctx\"> }</div><div class=\"diff-line diff-ctx\"> </div><div class=\"diff-line diff-add\">+.fiscal-confirm-modal {</div><div class=\"diff-line diff-add\">+    border-radius: 20px 20px 0 0;</div><div class=\"diff-line diff-add\">+    background: #f8f9fb;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-confirm-modal .modal-body {</div><div class=\"diff-line diff-add\">+    padding-top: 0.5rem;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-badges .badge {</div><div class=\"diff-line diff-add\">+    font-size: 0.72rem;</div><div class=\"diff-line diff-add\">+    padding: 0.35em 0.65em;</div><div class=\"diff-line diff-add\">+    border-radius: 8px;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-badge-doc {</div><div class=\"diff-line diff-add\">+    background: #0d6efd;</div><div class=\"diff-line diff-add\">+    color: #fff;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-badge-engine {</div><div class=\"diff-line diff-add\">+    background: #e9ecef;</div><div class=\"diff-line diff-add\">+    color: #495057;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-preview img {</div><div class=\"diff-line diff-add\">+    max-height: 160px;</div><div class=\"diff-line diff-add\">+    border-radius: 12px;</div><div class=\"diff-line diff-add\">+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-compliance-card {</div><div class=\"diff-line diff-add\">+    background: #fff;</div><div class=\"diff-line diff-add\">+    border-radius: 12px;</div><div class=\"diff-line diff-add\">+    padding: 12px 14px;</div><div class=\"diff-line diff-add\">+    border: 1px solid #e2e6ea;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-compliance-bar {</div><div class=\"diff-line diff-add\">+    height: 8px;</div><div class=\"diff-line diff-add\">+    background: #e9ecef;</div><div class=\"diff-line diff-add\">+    border-radius: 4px;</div><div class=\"diff-line diff-add\">+    overflow: hidden;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-compliance-fill {</div><div class=\"diff-line diff-add\">+    height: 100%;</div><div class=\"diff-line diff-add\">+    width: 0;</div><div class=\"diff-line diff-add\">+    background: linear-gradient(90deg, #dc3545, #ffc107, #198754);</div><div class=\"diff-line diff-add\">+    border-radius: 4px;</div><div class=\"diff-line diff-add\">+    transition: width 0.4s ease;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-section {</div><div class=\"diff-line diff-add\">+    background: #fff;</div><div class=\"diff-line diff-add\">+    border-radius: 14px;</div><div class=\"diff-line diff-add\">+    padding: 14px;</div><div class=\"diff-line diff-add\">+    margin-bottom: 12px;</div><div class=\"diff-line diff-add\">+    border: 1px solid #e8eaed;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-section-title {</div><div class=\"diff-line diff-add\">+    font-size: 0.78rem;</div><div class=\"diff-line diff-add\">+    font-weight: 700;</div><div class=\"diff-line diff-add\">+    text-transform: uppercase;</div><div class=\"diff-line diff-add\">+    letter-spacing: 0.04em;</div><div class=\"diff-line diff-add\">+    color: #6c757d;</div><div class=\"diff-line diff-add\">+    margin-bottom: 12px;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-field .form-label {</div><div class=\"diff-line diff-add\">+    font-size: 0.82rem;</div><div class=\"diff-line diff-add\">+    margin-bottom: 0.25rem;</div><div class=\"diff-line diff-add\">+    color: #495057;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-field--ok .form-control,</div><div class=\"diff-line diff-add\">+.fiscal-field--ok .form-select {</div><div class=\"diff-line diff-add\">+    border-color: #198754;</div><div class=\"diff-line diff-add\">+    background-color: rgba(25, 135, 84, 0.04);</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-field--review .form-control,</div><div class=\"diff-line diff-add\">+.fiscal-field--review .form-select {</div><div class=\"diff-line diff-add\">+    border-color: #ffc107;</div><div class=\"diff-line diff-add\">+    background-color: rgba(255, 193, 7, 0.08);</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-field--missing .form-control,</div><div class=\"diff-line diff-add\">+.fiscal-field--missing .form-select {</div><div class=\"diff-line diff-add\">+    border-color: #dc3545;</div><div class=\"diff-line diff-add\">+    background-color: rgba(220, 53, 69, 0.06);</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-alert-item {</div><div class=\"diff-line diff-add\">+    border-radius: 10px;</div><div class=\"diff-line diff-add\">+    padding: 8px 12px;</div><div class=\"diff-line diff-add\">+    margin-bottom: 6px;</div><div class=\"diff-line diff-add\">+    font-size: 0.82rem;</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-alert-error {</div><div class=\"diff-line diff-add\">+    background: rgba(220, 53, 69, 0.1);</div><div class=\"diff-line diff-add\">+    color: #842029;</div><div class=\"diff-line diff-add\">+    border: 1px solid rgba(220, 53, 69, 0.25);</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-add\">+.fiscal-alert-warning {</div><div class=\"diff-line diff-add\">+    background: rgba(255, 193, 7, 0.12);</div><div class=\"diff-line diff-add\">+    color: #664d03;</div><div class=\"diff-line diff-add\">+    border: 1px solid rgba(255, 193, 7, 0.35);</div><div class=\"diff-line diff-add\">+}</div><div class=\"diff-line diff-add\">+</div><div class=\"diff-line diff-ctx\"> @media (min-width: 768px) {</div><div class=\"diff-line diff-ctx\">     .mobile-main {</div><div class=\"diff-line diff-ctx\">         margin: 0 auto;</div></div>","linesAdded":113,"linesRemoved":0,"status":"pending"},"pending":9},"at":1782350833085},{"event":"status","data":{"text":"Límite de 80 herramientas alcanzado — finalizando con lo obtenido."},"at":1782350833085},{"event":"status","data":{"text":"Turno añadido al transcript del IDE (reabrir chat para verlo)"},"at":1782350833090},{"event":"done","data":{"ok":true,"bridgeSessionId":"bs-1782350833088-h3lam9","agentId":"agent-9cc504bf-bc48-4e49-adc3-52af03f84bef","runId":"run-36551d66-c5af-4367-9e45-d500b55f3cb8","status":"cancelled","result":"","changeSessionId":"cs-1782350776480-l80a0v","pendingChanges":9,"changes":[{"id":"ch-1","callId":"tool_c5ad3189-16bb-4e1a-a16c-e89401635e7","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@@ -340,19 +340,38 @@\n \n     private static function formatItem(AiExpenseQueue $queue, array $extracted = []): array\n     {\n+        $warnings = $extracted['validation_warnings'] ?? $extracted['warnings'] ?? [];\n+        $errors = $extracted['validation_errors'] ?? [];\n+\n         return [\n             'id' => $queue->id,\n             'doc_type' => $queue->doc_type,\n+            'document_kind' => $extracted['document_kind'] ?? self::inferDocumentKind($queue->doc_type),\n             'supplier_name' => $queue->supplier_name,\n             'supplier_cif' => $queue->supplier_cif,\n+            'counterparty_address' => $extracted['counterparty_address'] ?? '',\n             'invoice_number' => $queue->invoice_number,\n+            'invoice_series' => $extracted['invoice_series'] ?? '',\n             'invoice_date' => $queue->invoice_date,\n+            'operation_date' => $extracted['operation_date'] ?? $queue->invoice_date,\n+            'concept' => $extracted['concept'] ?? '',\n             'base_amount' => $queue->base_amount,\n+            'tax_rate' => $extracted['tax_rate'] ?? 21,\n             'tax_amount' => $queue->tax_amount,\n+            'irpf_rate' => $extracted['irpf_rate'] ?? null,\n+            'irpf_amount' => $extracted['irpf_amount'] ?? null,\n             'total_amount' => $queue->total_amount,\n+            'currency' => $extracted['currency'] ?? 'EUR',\n+            'payment_method' => $extracted['payment_method'] ?? '',\n+            'is_rectifying' => (bool) ($extracted['is_rectifying'] ?? false),\n+            'deductible' => (bool) ($extracted['deductible'] ?? $queue->doc_type === 'expense'),\n             'confidence' => $queue->confidence,\n+            'fiscal_compliance' => $extracted['fiscal_compliance'] ?? null,\n+            'field_status' => $extracted['field_status'] ?? [],\n+            'validation_errors' => $errors,\n+            'validation_warnings' => $warnings,\n             'notes' => $extracted['notes'] ?? '',\n-            'warnings' => $extracted['warnings'] ?? [],\n+            'warnings' => $warnings,\n             'ocr_engine' => $extracted['ocr_engine'] ?? '',\n             'image_url' => $queue->image_path\n                 ? Tools::config('route') . '/' . $queue->image_path\n@@ -360,6 +379,108 @@\n         ];\n     }\n \n+    private static function inferDocumentKind(string $docType): string\n+    {\n+        return $docType === 'issued' ? 'issued_invoice' : 'expense_received';\n+    }\n+\n+    /** @param array<string, mixed> $data */\n+    private static function enrichFiscalValidation(array $data): array\n+    {\n+        if (!empty($data['_error'])) {\n+            return $data;\n+        }\n+\n+        if (empty($data['supplier_name']) && !empty($data['counterparty_name'])) {\n+            $data['supplier_name'] = trim((string) $data['counterparty_name']);\n+        }\n+        if (empty($data['supplier_cif']) && !empty($data['counterparty_cif'])) {\n+            $data['supplier_cif'] = trim((string) $data['counterparty_cif']);\n+        }\n+\n+        $cif = strtoupper(trim((string) ($data['supplier_cif'] ?? '')));\n+        if ($cif !== '') {\n+            $data['supplier_cif'] = $cif;\n+        }\n+\n+        if (!isset($data['field_status']) || !is_array($data['field_status'])) {\n+            $data['field_status'] = [];\n+        }\n+\n+        if ($cif !== '') {\n+            $validator = new FiscalNumberValidator();\n+            $data['field_status']['counterparty_cif'] = $validator->isValid($cif) ? 'ok' : 'review';\n+        } elseif (!isset($data['field_status']['counterparty_cif'])) {\n+            $data['field_status']['counterparty_cif'] = 'review';\n+        }\n+\n+        if (empty($data['field_status'])) {\n+            $data['field_status'] = self::computeFieldStatus($data);\n+        }\n+\n+        if (!isset($data['fiscal_compliance'])) {\n+            $data['fiscal_compliance'] = self::computeCompliance($data['field_status']);\n+        }\n+\n+        $warnings = $data['validation_warnings'] ?? $data['warnings'] ?? [];\n+        if (!is_array($warnings)) {\n+            $warnings = [];\n+        }\n+\n+        if ($cif !== '' && ($data['field_status']['counterparty_cif'] ?? '') === 'review') {\n+            $warnings[] = 'El NIF/CIF no supera la validación algorítmica española. Revísalo.';\n+        }\n+\n+        $data['validation_warnings'] = array_values(array_unique($warnings));\n+        $data['warnings'] = $data['validation_warnings'];\n+\n+        return $data;\n+    }\n+\n+    /** @param array<string, mixed> $data */\n+    private static function computeFieldStatus(array $data): array\n+    {\n+        $docKind = $data['document_kind'] ?? 'expense_received';\n+        $isIssued = $docKind === 'issued_invoice' || ($data['doc_type'] ?? '') === 'issued';\n+\n+        $required = $isIssued\n+            ? ['counterparty_name', 'invoice_number', 'invoice_date', 'total_amount']\n+            : ['counterparty_name', 'invoice_date', 'total_amount'];\n+\n+        $status = [];\n+        foreach ($required as $field) {\n+            $value = $data[$field] ?? null;\n+            if ($field === 'counterparty_name' && empty($value)) {\n+                $value = $data['supplier_name'] ?? null;\n+            }\n+            if ($field === 'total_amount' && empty($value)) {\n+                $value = $data['total_amount'] ?? null;\n+            }\n+            $status[$field] = ($value !== null && $value !== '' && $value !== 0) ? 'ok' : 'missing';\n+        }\n+\n+        return $status;\n+    }\n+\n+    /** @param array<string, string> $fieldStatus */\n+    private static function computeCompliance(array $fieldStatus): float\n+    {\n+        if ($fieldStatus === []) {\n+            return 0.5;\n+        }\n+\n+        $compliance = 1.0;\n+        foreach ($fieldStatus as $state) {\n+            if ($state === 'missing') {\n+                $compliance -= 0.25;\n+            } elseif ($state === 'review') {\n+                $compliance -= 0.1;\n+            }\n+        }\n+\n+        return max(0.0, min(1.0, round($compliance, 2)));\n+    }\n+\n     private static function callAiService(string $filePath, string $docType, string $mode = MobileConfig::MODE_OCR): array\n     {\n         $serviceUrl = MobileConfig::aiServiceUrl();","linesAdded":122,"linesRemoved":1,"status":"pending"},{"id":"ch-2","callId":"tool_a434903d-4c74-4b08-bb61-7e457e25cb3","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@@ -497,6 +497,7 @@\n                 'doc_type' => $docType,\n                 'mode' => $mode,\n                 'ollama_url' => MobileConfig::ollamaUrl(),\n+                'ollama_model' => MobileConfig::ollamaModel(),\n                 'file' => new \\CURLFile(\n                     $filePath,\n                     self::guessMimeType($filePath),","linesAdded":1,"linesRemoved":0,"status":"pending"},{"id":"ch-3","callId":"tool_b1eb7670-236d-45a1-8a6f-94c0ee95ac3","path":"ai-service\\main.py","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\ai-service\\main.py\n@@ -661,15 +661,19 @@\n     result = _extract_from_text(text, doc_type)\n     result[\"ocr_engine\"] = engine\n \n+    llm_url = (ollama_url or DEFAULT_OLLAMA_URL).strip()\n+    llm_model = (ollama_model or DEFAULT_OLLAMA_MODEL).strip()\n+    llm_data = _ollama_extract(text, doc_type, llm_url, llm_model)\n+\n     if mode == \"ai\":\n-        llm_url = (ollama_url or DEFAULT_OLLAMA_URL).strip()\n-        llm_model = (ollama_model or DEFAULT_OLLAMA_MODEL).strip()\n-        llm_data = _ollama_extract(text, doc_type, llm_url, llm_model)\n         result = _merge_extractions(result, llm_data)\n         if llm_data is None:\n             result.setdefault(\"validation_warnings\", []).append(\n                 \"Ollama no respondió; se usaron solo reglas OCR. Comprueba la red LAN (192.168.1.16:11434).\"\n             )\n+    elif llm_data is not None:\n+        result = _merge_extractions(result, llm_data)\n+        result[\"ocr_engine\"] = (result.get(\"ocr_engine\") or engine) + \"+ollama\"\n \n     result = _validate_fiscal_data(result)\n     result[\"source_file\"] = filename","linesAdded":7,"linesRemoved":3,"status":"pending"},{"id":"ch-4","callId":"tool_8109de43-b49c-4d43-8265-b76dc76a89a","path":"Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n@@ -23,6 +23,9 @@\n         $baseAmount = self::toFloat($data['base_amount'] ?? $queue->base_amount);\n         $taxAmount = self::toFloat($data['tax_amount'] ?? $queue->tax_amount);\n         $totalAmount = self::toFloat($data['total_amount'] ?? $queue->total_amount);\n+        $taxRate = self::toFloat($data['tax_rate'] ?? 21);\n+        $irpfRate = self::toFloat($data['irpf_rate'] ?? 0);\n+        $concept = trim($data['concept'] ?? 'Gasto: ' . $supplierName);\n \n         if ($supplierName === '') {\n             return ['ok' => false, 'message' => 'Indica el nombre del proveedor'];","linesAdded":3,"linesRemoved":0,"status":"pending"},{"id":"ch-5","callId":"tool_11530fda-8770-4379-a01a-7b59a458f8a","path":"Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n@@ -39,11 +39,12 @@\n             if ($taxAmount > 0) {\n                 $baseAmount = round($totalAmount - $taxAmount, 2);\n             } else {\n-                $baseAmount = round($totalAmount / 1.21, 2);\n+                $rateFactor = 1 + ($taxRate / 100);\n+                $baseAmount = round($totalAmount / $rateFactor, 2);\n                 $taxAmount = round($totalAmount - $baseAmount, 2);\n             }\n         } elseif ($taxAmount <= 0 && $baseAmount > 0 && $totalAmount <= 0) {\n-            $taxAmount = round($baseAmount * 0.21, 2);\n+            $taxAmount = round($baseAmount * ($taxRate / 100), 2);\n             $totalAmount = round($baseAmount + $taxAmount, 2);\n         }\n ","linesAdded":3,"linesRemoved":2,"status":"pending"},{"id":"ch-6","callId":"tool_cab6aa97-ad92-4ed4-85b7-97bea0fb4e8","path":"Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n@@ -64,13 +64,13 @@\n         }\n \n         $line = $invoice->getNewLine();\n-        $line->descripcion = 'Gasto: ' . $supplierName;\n+        $line->descripcion = $concept;\n         $line->cantidad = 1;\n         $line->pvpunitario = $baseAmount;\n-        $line->codimpuesto = self::defaultTaxCode();\n+        $line->codimpuesto = self::taxCodeFromRate($taxRate);\n         $line->dtopor = 0;\n         $line->dtopor2 = 0;\n-        $line->irpf = 0;\n+        $line->irpf = $irpfRate > 0 ? $irpfRate : 0;\n \n         if (!$line->save()) {\n             return ['ok' => false, 'message' => 'No se pudo crear la línea de factura'];","linesAdded":3,"linesRemoved":3,"status":"pending"},{"id":"ch-7","callId":"tool_19a2c6f2-c04f-49c3-84d0-5bf6bd7736f","path":"Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php","type":"edit","diffString":"--- a/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n+++ b/C:\\xampp\\htdocs\\ALimpiezas\\Plugins\\ALimpiezasMobile\\Lib\\ExpenseInvoiceCreator.php\n@@ -145,6 +145,18 @@\n         }\n     }\n \n+    private static function taxCodeFromRate(float $rate): string\n+    {\n+        $rate = (int) round($rate);\n+\n+        return match ($rate) {\n+            10 => 'IVA10',\n+            4 => 'IVA4',\n+            0 => 'IVA0',\n+            default => 'IVA21',\n+        };\n+    }\n+\n     private static function defaultTaxCode(): string\n     {\n         return 'IVA21';","linesAdded":12,"linesRemoved":0,"status":"pending"},{"id":"ch-8","callId":"tool_aefe9c8b-7b3f-4fe5-addb-75e20cc7d14","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@@ -96,54 +96,148 @@\n     </div>\n \n     <div class=\"modal fade\" id=\"confirmModal\" tabindex=\"-1\" aria-labelledby=\"confirmModalLabel\">\n-        <div class=\"modal-dialog modal-dialog-centered modal-fullscreen-sm-down\">\n-            <div class=\"modal-content\" data-guide=\"expense-confirm\">\n-                <div class=\"modal-header\">\n-                    <h5 class=\"modal-title\" id=\"confirmModalLabel\">Confirmar datos</h5>\n-                    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"></button>\n-                </div>\n-                <div class=\"modal-body\">\n-                    <div id=\"confirm-preview-image\" class=\"mb-3 text-center\"></div>\n-                    <form id=\"confirm-form\">\n-                        <input type=\"hidden\" name=\"queue_id\" id=\"confirm-queue-id\">\n-                        <div class=\"mb-2\">\n-                            <label class=\"form-label\">Proveedor / emisor</label>\n-                            <input type=\"text\" class=\"form-control\" name=\"supplier_name\" id=\"cf-supplier-name\" required>\n+        <div class=\"modal-dialog modal-dialog-centered modal-fullscreen-sm-down modal-dialog-scrollable\">\n+            <div class=\"modal-content fiscal-confirm-modal\" data-guide=\"expense-confirm\">\n+                <div class=\"modal-header border-0 pb-0\">\n+                    <div class=\"w-100\">\n+                        <div class=\"d-flex align-items-center justify-content-between gap-2 mb-2\">\n+                            <h5 class=\"modal-title mb-0\" id=\"confirmModalLabel\">Revisión fiscal</h5>\n+                            <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Cerrar\"></button>\n                         </div>\n-                        <div class=\"mb-2\">\n-                            <label class=\"form-label\">NIF/CIF</label>\n-                            <input type=\"text\" class=\"form-control\" name=\"supplier_cif\" id=\"cf-supplier-cif\">\n+                        <div class=\"fiscal-badges d-flex flex-wrap gap-2\">\n+                            <span class=\"badge fiscal-badge-doc\" id=\"cf-doc-kind-badge\">Factura recibida</span>\n+                            <span class=\"badge fiscal-badge-engine\" id=\"cf-engine-badge\"></span>\n                         </div>\n-                        <div class=\"mb-2\">\n-                            <label class=\"form-label\">Nº factura</label>\n-                            <input type=\"text\" class=\"form-control\" name=\"invoice_number\" id=\"cf-invoice-number\">\n+                    </div>\n+                </div>\n+                <div class=\"modal-body pt-2\">\n+                    <div id=\"confirm-preview-image\" class=\"fiscal-preview mb-3\"></div>\n+\n+                    <div class=\"fiscal-compliance-card mb-3\" id=\"cf-compliance-wrap\">\n+                        <div class=\"d-flex justify-content-between align-items-center mb-1\">\n+                            <span class=\"small fw-semibold\">Cumplimiento fiscal</span>\n+                            <span class=\"small\" id=\"cf-compliance-pct\">—</span>\n                         </div>\n-                        <div class=\"mb-2\">\n-                            <label class=\"form-label\">Fecha</label>\n-                            <input type=\"date\" class=\"form-control\" name=\"invoice_date\" id=\"cf-invoice-date\">\n+                        <div class=\"fiscal-compliance-bar\">\n+                            <div class=\"fiscal-compliance-fill\" id=\"cf-compliance-fill\"></div>\n                         </div>\n-                        <div class=\"row g-2\">\n-                            <div class=\"col-4\">\n-                                <label class=\"form-label\">Base €</label>\n-                                <input type=\"number\" step=\"0.01\" class=\"form-control\" name=\"base_amount\" id=\"cf-base\">\n+                        <div class=\"small text-muted mt-1\" id=\"cf-confidence-text\"></div>\n+                    </div>\n+\n+                    <div id=\"cf-alerts\" class=\"mb-3\"></div>\n+\n+                    <form id=\"confirm-form\">\n+                        <input type=\"hidden\" name=\"queue_id\" id=\"confirm-queue-id\">\n+                        <input type=\"hidden\" name=\"doc_type\" id=\"cf-doc-type\" value=\"expense\">\n+\n+                        <section class=\"fiscal-section\">\n+                            <h6 class=\"fiscal-section-title\">Identificación (RD 1619/2012)</h6>\n+                            <div class=\"mb-2 fiscal-field\" data-field=\"counterparty_name\">\n+                                <label class=\"form-label\">Emisor / proveedor</label>\n+                                <input type=\"text\" class=\"form-control\" name=\"supplier_name\" id=\"cf-supplier-name\" required>\n                             </div>\n-                            <div class=\"col-4\">\n-                                <label class=\"form-label\">IVA €</label>\n-                                <input type=\"number\" step=\"0.01\" class=\"form-control\" name=\"tax_amount\" id=\"cf-tax\">\n+                            <div class=\"mb-2 fiscal-field\" data-field=\"counterparty_cif\">\n+                                <label class=\"form-label\">NIF / CIF emisor</label>\n+                                <input type=\"text\" class=\"form-control\" name=\"supplier_cif\" id=\"cf-supplier-cif\" placeholder=\"B12345678\">\n                             </div>\n-                            <div class=\"col-4\">\n-                                <label class=\"form-label\">Total €</label>\n-                                <input type=\"number\" step=\"0.01\" class=\"form-control\" name=\"total_amount\" id=\"cf-total\">\n+                            <div class=\"mb-2 fiscal-field\" data-field=\"counterparty_address\">\n+                                <label class=\"form-label\">Domicilio emisor</label>\n+                                <input type=\"text\" class=\"form-control\" name=\"counterparty_address\" id=\"cf-address\" placeholder=\"Opcional en ticket simplificado\">\n                             </div>\n-                        </div>\n-                        <div class=\"alert alert-info mt-3 small mb-0\" id=\"cf-notes\"></div>\n+                        </section>\n+\n+                        <section class=\"fiscal-section\">\n+                            <h6 class=\"fiscal-section-title\">Datos del documento</h6>\n+                            <div class=\"row g-2 mb-2\">\n+                                <div class=\"col-6 fiscal-field\" data-field=\"invoice_series\">\n+                                    <label class=\"form-label\">Serie</label>\n+                                    <input type=\"text\" class=\"form-control\" name=\"invoice_series\" id=\"cf-invoice-series\">\n+                                </div>\n+                                <div class=\"col-6 fiscal-field\" data-field=\"invoice_number\">\n+                                    <label class=\"form-label\">Nº factura</label>\n+                                    <input type=\"text\" class=\"form-control\" name=\"invoice_number\" id=\"cf-invoice-number\">\n+                                </div>\n+                            </div>\n+                            <div class=\"row g-2 mb-2\">\n+                                <div class=\"col-6 fiscal-field\" data-field=\"invoice_date\">\n+                                    <label class=\"form-label\">Fecha factura</label>\n+                                    <input type=\"date\" class=\"form-control\" name=\"invoice_date\" id=\"cf-invoice-date\">\n+                                </div>\n+                                <div class=\"col-6 fiscal-field\" data-field=\"operation_date\">\n+                                    <label class=\"form-label\">Fecha operación</label>\n+                                    <input type=\"date\" class=\"form-control\" name=\"operation_date\" id=\"cf-operation-date\">\n+                                </div>\n+                            </div>\n+                            <div class=\"mb-2 fiscal-field\" data-field=\"concept\">\n+                                <label class=\"form-label\">Concepto / descripción</label>\n+                                <input type=\"text\" class=\"form-control\" name=\"concept\" id=\"cf-concept\" placeholder=\"Descripción del gasto o servicio\">\n+                            </div>\n+                        </section>\n+\n+                        <section class=\"fiscal-section\">\n+                            <h6 class=\"fiscal-section-title\">Importes (Ley IVA)</h6>\n+                            <div class=\"row g-2 mb-2\">\n+                                <div class=\"col-6 fiscal-field\" data-field=\"base_amount\">\n+                                    <label class=\"form-label\">Base imponible €</label>\n+                                    <input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount\" name=\"base_amount\" id=\"cf-base\">\n+                                </div>\n+                                <div class=\"col-6 fiscal-field\" data-field=\"tax_rate\">\n+                                    <label class=\"form-label\">Tipo IVA</label>\n+                                    <select class=\"form-select fiscal-amount\" name=\"tax_rate\" id=\"cf-tax-rate\">\n+                                        <option value=\"21\">21 % (general)</option>\n+                                        <option value=\"10\">10 % (reducido)</option>\n+                                        <option value=\"4\">4 % (superreducido)</option>\n+                                        <option value=\"0\">0 % (exento)</option>\n+                                    </select>\n+                                </div>\n+                            </div>\n+                            <div class=\"row g-2 mb-2\">\n+                                <div class=\"col-4 fiscal-field\" data-field=\"tax_amount\">\n+                                    <label class=\"form-label\">Cuota IVA €</label>\n+                                    <input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount\" name=\"tax_amount\" id=\"cf-tax\">\n+                                </div>\n+                                <div class=\"col-4 fiscal-field\" data-field=\"irpf_amount\">\n+                                    <label class=\"form-label\">IRPF €</label>\n+                                    <input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount\" name=\"irpf_amount\" id=\"cf-irpf-amount\">\n+                                </div>\n+                                <div class=\"col-4 fiscal-field\" data-field=\"total_amount\">\n+                                    <label class=\"form-label\">Total €</label>\n+                                    <input type=\"number\" step=\"0.01\" class=\"form-control fiscal-amount fw-bold\" name=\"total_amount\" id=\"cf-total\" required>\n+                                </div>\n+                            </div>\n+                            <div class=\"mb-2 fiscal-field\" data-field=\"irpf_rate\">\n+                                <label class=\"form-label\">Retención IRPF %</label>\n+                                <select class=\"form-select\" name=\"irpf_rate\" id=\"cf-irpf-rate\">\n+                                    <option value=\"0\">Sin retención</option>\n+                                    <option value=\"7\">7 %</option>\n+                                    <option value=\"15\">15 %</option>\n+                                    <option value=\"19\">19 %</option>\n+                                </select>\n+                            </div>\n+                        </section>\n+\n+                        <section class=\"fiscal-section\">\n+                            <h6 class=\"fiscal-section-title\">Clasificación tributaria</h6>\n+                            <div class=\"d-flex flex-wrap gap-3\">\n+                                <div class=\"form-check\">\n+                                    <input class=\"form-check-input\" type=\"checkbox\" name=\"deductible\" id=\"cf-deductible\" value=\"1\">\n+                                    <label class=\"form-check-label\" for=\"cf-deductible\">Gasto deducible</label>\n+                                </div>\n+                                <div class=\"form-check\">\n+                                    <input class=\"form-check-input\" type=\"checkbox\" name=\"is_rectifying\" id=\"cf-rectifying\" value=\"1\">\n+                                    <label class=\"form-check-label\" for=\"cf-rectifying\">Factura rectificativa</label>\n+                                </div>\n+                            </div>\n+                        </section>\n+\n+                        <div class=\"alert alert-light border mt-3 small mb-0\" id=\"cf-notes\"></div>\n                     </form>\n                 </div>\n-                <div class=\"modal-footer d-grid gap-2\">\n+                <div class=\"modal-footer border-0 d-grid gap-2 pt-0\">\n                     <button type=\"button\" class=\"btn btn-success btn-lg\" id=\"btn-confirm-ok\">\n-                        <i class=\"fa-solid fa-check me-2\"></i>Confirmar y crear factura\n+                        <i class=\"fa-solid fa-check me-2\"></i>Confirmar y registrar\n                     </button>\n-                    <button type=\"button\" class=\"btn btn-outline-danger\" id=\"btn-confirm-reject\">Rechazar</button>\n+                    <button type=\"button\" class=\"btn btn-outline-danger\" id=\"btn-confirm-reject\">Rechazar documento</button>\n                 </div>\n             </div>\n         </div>","linesAdded":131,"linesRemoved":37,"status":"pending"},{"id":"ch-9","callId":"tool_f23db580-c812-4445-bb9d-02a5b533f03","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@@ -218,6 +218,119 @@\n     border-radius: 16px;\n }\n \n+.fiscal-confirm-modal {\n+    border-radius: 20px 20px 0 0;\n+    background: #f8f9fb;\n+}\n+\n+.fiscal-confirm-modal .modal-body {\n+    padding-top: 0.5rem;\n+}\n+\n+.fiscal-badges .badge {\n+    font-size: 0.72rem;\n+    padding: 0.35em 0.65em;\n+    border-radius: 8px;\n+}\n+\n+.fiscal-badge-doc {\n+    background: #0d6efd;\n+    color: #fff;\n+}\n+\n+.fiscal-badge-engine {\n+    background: #e9ecef;\n+    color: #495057;\n+}\n+\n+.fiscal-preview img {\n+    max-height: 160px;\n+    border-radius: 12px;\n+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);\n+}\n+\n+.fiscal-compliance-card {\n+    background: #fff;\n+    border-radius: 12px;\n+    padding: 12px 14px;\n+    border: 1px solid #e2e6ea;\n+}\n+\n+.fiscal-compliance-bar {\n+    height: 8px;\n+    background: #e9ecef;\n+    border-radius: 4px;\n+    overflow: hidden;\n+}\n+\n+.fiscal-compliance-fill {\n+    height: 100%;\n+    width: 0;\n+    background: linear-gradient(90deg, #dc3545, #ffc107, #198754);\n+    border-radius: 4px;\n+    transition: width 0.4s ease;\n+}\n+\n+.fiscal-section {\n+    background: #fff;\n+    border-radius: 14px;\n+    padding: 14px;\n+    margin-bottom: 12px;\n+    border: 1px solid #e8eaed;\n+}\n+\n+.fiscal-section-title {\n+    font-size: 0.78rem;\n+    font-weight: 700;\n+    text-transform: uppercase;\n+    letter-spacing: 0.04em;\n+    color: #6c757d;\n+    margin-bottom: 12px;\n+}\n+\n+.fiscal-field .form-label {\n+    font-size: 0.82rem;\n+    margin-bottom: 0.25rem;\n+    color: #495057;\n+}\n+\n+.fiscal-field--ok .form-control,\n+.fiscal-field--ok .form-select {\n+    border-color: #198754;\n+    background-color: rgba(25, 135, 84, 0.04);\n+}\n+\n+.fiscal-field--review .form-control,\n+.fiscal-field--review .form-select {\n+    border-color: #ffc107;\n+    background-color: rgba(255, 193, 7, 0.08);\n+}\n+\n+.fiscal-field--missing .form-control,\n+.fiscal-field--missing .form-select {\n+    border-color: #dc3545;\n+    background-color: rgba(220, 53, 69, 0.06);\n+}\n+\n+.fiscal-alert-item {\n+    border-radius: 10px;\n+    padding: 8px 12px;\n+    margin-bottom: 6px;\n+    font-size: 0.82rem;\n+}\n+\n+.fiscal-alert-error {\n+    background: rgba(220, 53, 69, 0.1);\n+    color: #842029;\n+    border: 1px solid rgba(220, 53, 69, 0.25);\n+}\n+\n+.fiscal-alert-warning {\n+    background: rgba(255, 193, 7, 0.12);\n+    color: #664d03;\n+    border: 1px solid rgba(255, 193, 7, 0.35);\n+}\n+\n @media (min-width: 768px) {\n     .mobile-main {\n         margin: 0 auto;","linesAdded":113,"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 donde se cortó: reviso el estado actual de la extracción fiscal con IA y la UI de confirmación móvil.\nCompletan"},"at":1782350833093}]}
