# `Sat.Csf`

Extrae datos estructurados de una **Constancia de Situación Fiscal (CSF)**
emitida por el SAT a partir de su PDF.

## Uso

    {:ok, %Sat.Csf.Document{} = csf} = Sat.Csf.from_file("priv/csf.pdf")

    csf.identificacion.rfc
    #=> "MACA961017759"

    csf.regimenes
    #=> [%Sat.Csf.Regimen{regimen: "...", codigo: "626", ...}, ...]

Bajo el capó usa `Pdf.Reader.read/2` con `dictionary: :es` para reconstruir
el texto correctamente. El parser de la respuesta (`Sat.Csf.Parser`) usa
posiciones X de tokens para separar columnas en las tablas de obligaciones.

## Errores

- `{:error, :not_a_csf}` — el PDF no contiene los marcadores de sección
  de un CSF (no se detectó "Datos de Identificación del Contribuyente").
- cualquier error reportado por `Pdf.Reader.open/2` o `Pdf.Reader.read/2`.

# `from_result`

```elixir
@type from_result() :: {:ok, Sat.Csf.Document.t()} | {:error, term()}
```

# `from_binary`

```elixir
@spec from_binary(
  binary(),
  keyword()
) :: from_result()
```

Lee y parsea un CSF desde un binario `.pdf` ya cargado en memoria.

# `from_file`

```elixir
@spec from_file(
  Path.t(),
  keyword()
) :: from_result()
```

Lee y parsea un CSF desde una ruta de archivo.

Las opciones se pasan a `Pdf.Reader.read/2`. Por defecto se incluye
`dictionary: :es` para que el extractor separe palabras pegadas (puedes
sobrescribirlo pasando `dictionary: nil` o tu propia `MapSet`).

# `from_result`

```elixir
@spec from_result(Pdf.Reader.Result.t()) :: from_result()
```

Parsea un `%Pdf.Reader.Result{}` ya extraído. Útil si ya estás trabajando
con el reader y quieres evitar reabrir el PDF.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
