isValid()) { throw new Exception("El archivo no es válido o está corrupto."); } // Validamos que sea XML if ($file->getClientOriginalExtension() !== 'xml') { throw new Exception("Solo se permiten archivos XML."); } // Cargar XML desde el contenido del archivo $xmlContent = file_get_contents($file->getRealPath()); $this->loadXml($xmlContent); // Extraer datos del XML return $this->extractData(); } /** * Carga el XML y lo convierte en un objeto SimpleXMLElement * * @param string $xmlContent * @throws Exception */ public function loadXml(string $xmlContent): void { try { $this->xml = new SimpleXMLElement($xmlContent); // Registrar espacios de nombres $this->xml->registerXPathNamespace('cfdi', 'http://www.sat.gob.mx/cfd/4'); $this->xml->registerXPathNamespace('tfd', 'http://www.sat.gob.mx/TimbreFiscalDigital'); } catch (Exception $e) { throw new Exception('Error al cargar el XML: ' . $e->getMessage()); } } /** * Extrae los datos clave de la factura XML * * @return array */ public function extractData(): array { if (!$this->xml) { throw new Exception('No se ha cargado un XML válido.'); } $this->data['comprobante'] = $this->extractComprobante(); $this->data['emisor'] = $this->extractEmisor(); $this->data['receptor'] = $this->extractReceptor(); $this->data['conceptos'] = $this->extractConceptos(); $this->data['impuestos'] = $this->extractImpuestos(); $this->data['timbreFiscal'] = $this->extractTimbreFiscal(); return $this->data; } protected function extractComprobante(): array { return [ 'fecha' => (string) $this->xml['Fecha'] ?? null, 'total' => (float) $this->xml['Total'] ?? 0.0, 'moneda' => (string) $this->xml['Moneda'] ?? 'MXN', 'tipoDeComprobante' => (string) $this->xml['TipoDeComprobante'] ?? null, 'metodoPago' => (string) $this->xml['MetodoPago'] ?? null, 'lugarExpedicion' => (string) $this->xml['LugarExpedicion'] ?? null, ]; } protected function extractEmisor(): array { $emisor = $this->xml->xpath('//cfdi:Emisor')[0] ?? null; if (!$emisor) return []; return [ 'rfc' => (string) $emisor['Rfc'] ?? null, 'nombre' => (string) $emisor['Nombre'] ?? null, 'regimenFiscal' => (string) $emisor['RegimenFiscal'] ?? null, ]; } protected function extractReceptor(): array { $receptor = $this->xml->xpath('//cfdi:Receptor')[0] ?? null; if (!$receptor) return []; return [ 'rfc' => (string) $receptor['Rfc'] ?? null, 'nombre' => (string) $receptor['Nombre'] ?? null, 'usoCFDI' => (string) $receptor['UsoCFDI'] ?? null, 'regimenFiscal' => (string) $receptor['RegimenFiscalReceptor'] ?? null, 'domicilioFiscal' => (string) $receptor['DomicilioFiscalReceptor'] ?? null, ]; } protected function extractConceptos(): array { $conceptos = []; foreach ($this->xml->xpath('//cfdi:Concepto') as $concepto) { $conceptos[] = [ 'descripcion' => (string) $concepto['Descripcion'] ?? null, 'cantidad' => (float) $concepto['Cantidad'] ?? 1.0, 'valorUnitario' => (float) $concepto['ValorUnitario'] ?? 0.0, 'importe' => (float) $concepto['Importe'] ?? 0.0, 'claveProdServ' => (string) $concepto['ClaveProdServ'] ?? null, 'claveUnidad' => (string) $concepto['ClaveUnidad'] ?? null, ]; } return $conceptos; } protected function extractImpuestos(): array { $impuestos = $this->xml->xpath('//cfdi:Impuestos')[0] ?? null; if (!$impuestos) return []; return [ 'totalImpuestosTrasladados' => (float) $impuestos['TotalImpuestosTrasladados'] ?? 0.0, 'totalImpuestosRetenidos' => (float) $impuestos['TotalImpuestosRetenidos'] ?? 0.0, ]; } protected function extractTimbreFiscal(): array { $timbre = $this->xml->xpath('//tfd:TimbreFiscalDigital')[0] ?? null; if (!$timbre) return []; return [ 'uuid' => (string) $timbre['UUID'] ?? null, 'fechaTimbrado' => (string) $timbre['FechaTimbrado'] ?? null, 'selloCFD' => (string) $timbre['SelloCFD'] ?? null, 'selloSAT' => (string) $timbre['SelloSAT'] ?? null, ]; } }