Pular para conteúdo

ECD — Bloco J (Demonstrações Contábeis)

Finalidade do Bloco

O Bloco J contém as demonstrações contábeis obrigatórias: - Balanço Patrimonial (J100) — composição de Ativo, Passivo e PL - DRE — Demonstração do Resultado do Exercício (J150) — receitas e despesas - Termo de Encerramento (J900) — formaliza o fechamento do livro - Signatário/Contador (J930) — identificação do responsável legal

Do ponto de vista regulatório, o Bloco J é o que a Receita Federal e auditores olham para validar a saúde financeira da empresa.


Registro J001 — Abertura do Bloco J

Finalidade

Indicador de presença de dados no Bloco J.

Campos

# Campo Valor Significado
1 IND_DAD '0' 🔒 Fixo — bloco com dados

Referência técnica

enviar_registro_J001() — linha 674


Registro J005 — Identificação das Demonstrações Contábeis

Finalidade

Identifica o período e o tipo (sequencial) das demonstrações contábeis que seguem nos registros J100, J150, etc.

Campos — detalhamento

# Campo SPED Valor Origem Significado legal Detalhes
1 DT_INI self.date_ini ⚙️ Wizard Data de início das demonstrações Formato DDMMAAAA
2 DT_FIN self.date_fim ⚙️ Wizard Data de fim das demonstrações Formato DDMMAAAA
3 ID_DEM 1 🔒 Fixo Identificação da demonstração. 1=primeira (e única). Se houver demonstrações adicionais (ex: consolidadas), seria 2, 3, etc. Sempre 1
4 CAB_DEM '' 🔒 Fixo Cabeçalho das demonstrações. Texto livre para identificação adicional. Sempre vazio

Referência técnica

enviar_registro_J005() — linha 681


Registro J100 — Balanço Patrimonial

Finalidade

Demonstra a composição do Ativo, Passivo e Patrimônio Líquido da empresa, com saldos iniciais e finais consolidados por grupo contábil. Cada grupo gera um registro J100.

Quando é gerado

Para cada grupo contábil (account.group) com l10n_br_cod_nat em ['01', '02', '03'] que possua saldo ≠ 0.

Filtro de grupos — detalhado

group_ids = self.env['account.group'].search([
    ('company_id', '=', self.company_id.id),
    ('l10n_br_cod_nat', 'in', ['01', '02', '03']),
])
Código Significado Classificação no J100
01 Contas de Ativo IND_GRP_BAL = 'A' (Ativo)
02 Contas de Passivo IND_GRP_BAL = 'P' (Passivo/PL)
03 Patrimônio Líquido IND_GRP_BAL = 'P' (Passivo/PL)

Convenção contábil: Patrimônio Líquido (03) é classificado como 'P' junto com Passivo, conforme a estrutura do Balanço Patrimonial brasileiro (Ativo = Passivo + PL).

Cálculo de saldos — usa child_of

O cálculo de saldos consolida todas as contas filhas do grupo recursivamente usando o operador child_of do ORM:

Saldo inicial (tudo ANTES do período):

saldo_inicial = AccountMoveLine.read_group(
    domain=[
        ('company_id', '=', company),
        ('account_id.group_id', 'child_of', group_id.id),  # Recursivo
        ('date', '<', self.date_ini),
        ('move_id.state', '=', 'posted'),
    ],
    fields=['debit', 'credit'],
    groupby=[]  # Sem agrupamento — soma consolidada
)
saldo_inicial_valor = (debit or 0) - (credit or 0)

Saldo final (tudo ATÉ o final do período, inclusive):

saldo_final = AccountMoveLine.read_group(
    domain=[
        ('company_id', '=', company),
        ('account_id.group_id', 'child_of', group_id.id),
        ('date', '<=', self.date_fim),  # Inclusive
        ('move_id.state', '=', 'posted'),
    ],
    fields=['debit', 'credit'],
    groupby=[]
)
saldo_final_valor = (debit or 0) - (credit or 0)

Diferença importante com o I155: O J100 usa child_of para consolidar todos os subgrupos e contas abaixo de cada grupo. O I155 calcula conta a conta individualmente.

Condição de supressão

if saldo_inicial_valor == 0 and saldo_final_valor == 0:
    continue  # Grupo ignorado — sem dados

Indicador Totalizador vs. Detalhe (IND_COD_AGL)

ind_cod_agl = 'T' if not bool(
    self.env['account.account'].search_count([('group_id', '=', group_id.id)])
) else 'D'
Valor Significado Condição
'T' Totalizador — grupo sem contas analíticas diretas search_count(group_id = grupo) == 0 (apenas subgrupos)
'D' Detalhe — grupo com contas analíticas diretamente vinculadas search_count(group_id = grupo) > 0

Observação: O search_count verifica apenas contas diretamente no grupo (não recursivo). Se um grupo tem subgrupos mas nenhuma conta direta, é classificado como 'T'.

Indicador Ativo/Passivo (IND_GRP_BAL)

IND_GRP_BAL = 'A' if group_id.l10n_br_cod_nat == '01' else 'P'
Natureza do grupo Valor Significado
01 (Ativo) 'A' Grupo pertence ao Ativo
02 (Passivo) 'P' Grupo pertence ao Passivo
03 (PL) 'P' Grupo pertence ao PL (convenção: fica no lado Passivo)

Cálculo do nível de aglutinação (NIVEL_AGL)

nivel_agl = 1
group_nivel_id = group_id
while group_nivel_id.parent_id:
    group_nivel_id = group_nivel_id.parent_id
    nivel_agl += 1

Campos — detalhamento completo

# Campo SPED Valor Cálculo Detalhes
1 COD_AGL group.code_prefix_start Cadastro Código de aglutinação (vinculado pelo I052)
2 IND_COD_AGL 'T' ou 'D' 🧮 Totalizador ou Detalhe
3 NIVEL_AGL Calculado 🧮 Percorre parent_id Nível na hierarquia
4 COD_AGL_SUP group.parent_id.code_prefix_start Cadastro Código do grupo pai. Vazio se raiz
5 IND_GRP_BAL 'A' ou 'P' 🧮 Ativo ou Passivo/PL
6 DESCR_COD_AGL group.name Cadastro Nome do grupo
7 VL_CTA_INI abs(saldo_inicial) 🧮 child_of Saldo inicial consolidado
8 IND_DC_CTA_INI 'D' se > 0, 'C' se ≤ 0 🧮 Natureza do saldo inicial
9 VL_CTA_FIN abs(saldo_final) 🧮 child_of Saldo final consolidado
10 IND_DC_CTA_FIN 'D' se > 0, 'C' se ≤ 0 🧮 Natureza do saldo final
11 NOTA_EXP_REF '' 🔒 Fixo Nota explicativa — sempre vazia

Problemas comuns

Problema Causa raiz Solução
Grupo não aparece no J100 l10n_br_cod_nat do grupo não é 01, 02 ou 03 Atribuir código da natureza no grupo
Valores consolidados incorretos Hierarquia de parent_id está errada Reorganizar parent_id dos grupos
Saldo zerado quando deveria ter valor Lançamentos não postados Postar lançamentos pendentes
IND_GRP_BAL incorreto Natureza 03 (PL) aparece como P Correto — PL fica no lado Passivo
Grupo aparece como T mas tem contas Contas estão em subgrupos, não diretamente neste grupo Correto — T indica "somente subgrupos"

Referência técnica

enviar_registro_J100() — linhas 695–756


Registro J150 — Demonstração do Resultado do Exercício (DRE)

Finalidade

Demonstra o resultado econômico (receitas e despesas) da empresa no período, consolidado por grupo contábil.

Quando é gerado

Para cada grupo com l10n_br_cod_nat = '04' (contas de resultado) com saldos ≠ 0.

Corrigido no commit 42d3a28f: O J150 filtra corretamente por cod_nat in ['04'] (resultado), diferente do J100 que filtra por ['01','02','03'] (Ativo/Passivo/PL).

Filtro de grupos

group_ids = self.env['account.group'].search([
    ('company_id', '=', self.company_id.id),
    ('l10n_br_cod_nat', 'in', ['04']),  # Somente resultado
])

Cálculo de saldos

Idêntico ao J100 — usa child_of para consolidar subgrupos e contas:

saldo_inicial = read_group(account_id.group_id child_of group, date < date_ini, posted)
saldo_final = read_group(account_id.group_id child_of group, date <= date_fim, posted)

Indicador Receita vs. Despesa (IND_GRP_DRE)

ind_grp_dre = 'D' if not bool(
    self.env['account.account'].search_count([
        ('group_id', 'child_of', group_id.id),
        ('account_type', 'in', ['income', 'income_other']),
    ])
) else 'R'
Valor Significado Condição
'R' Receita O grupo contém (recursivamente, via child_of) ao menos uma conta com account_type = 'income' ou 'income_other'
'D' Despesa O grupo não contém contas com tipo income

Nota: O child_of aqui é recursivo — se um grupo pai contém subgrupos com contas de receita, o grupo pai também será classificado como 'R'.

Diferença estrutural — NU_ORDEM

O J150 adiciona um sequencial (NU_ORDEM) que o J100 não tem:

num_ordem = 1
for group_id in group_ids:
    ...
    registroJ150.NU_ORDEM = num_ordem
    ...
    num_ordem += 1

Campos — detalhamento completo

# Campo SPED Valor Cálculo Detalhes
1 NU_ORDEM Sequencial 1, 2, 3... 🧮 Número de ordem da linha na DRE
2 COD_AGL group.code_prefix_start Cadastro Código de aglutinação
3 IND_COD_AGL 'T' ou 'D' 🧮 Mesma lógica do J100
4 NIVEL_AGL Calculado via parent_id 🧮 Profundidade do grupo
5 COD_AGL_SUP group.parent_id.code_prefix_start Cadastro Grupo pai
6 DESCR_COD_AGL group.name Cadastro Nome do grupo
7 VL_CTA_INI abs(saldo_inicial) 🧮 child_of Saldo inicial consolidado
8 IND_DC_CTA_INI 'D' se > 0, 'C' se ≤ 0 🧮 Natureza
9 VL_CTA_FIN abs(saldo_final) 🧮 child_of Saldo final consolidado
10 IND_DC_CTA_FIN 'D' se > 0, 'C' se ≤ 0 🧮 Natureza
11 IND_GRP_DRE 'R' ou 'D' 🧮 Receita ou Despesa
12 NOTA_EXP_REF '' 🔒 Fixo Nota explicativa

Tabela comparativa J100 vs. J150

Aspecto J100 (Balanço) J150 (DRE)
Filtro de natureza ['01','02','03'] (Ativo/Passivo/PL) ['04'] (Resultado)
Campo de classificação IND_GRP_BAL (A=Ativo, P=Passivo) IND_GRP_DRE (R=Receita, D=Despesa)
Sequencial ❌ Não tem NU_ORDEM
Cálculo de saldos child_of consolidado child_of consolidado (idêntico)
Condição de supressão saldo_ini == 0 AND saldo_fin == 0 saldo_ini == 0 AND saldo_fin == 0
Verificação de tipo de conta ❌ Não verifica ✅ Verifica income/income_other para R/D

Referência técnica

enviar_registro_J150() — linhas 758–824


Registro J900 — Termo de Encerramento do Livro Contábil

Finalidade

Formaliza o encerramento da escrituração contábil digital. Equivale ao Termo de Encerramento que antigamente era lavrado em cartório. Contém dados da empresa e a natureza do livro.

Campos — detalhamento

# Campo SPED Valor Origem Significado legal Recalculado?
1 REG J900 Fixo Identificador
2 DNRC_ENCER 'TERMO DE ENCERRAMENTO' 🔒 Fixo (CampoFixo na classe) Texto conforme instrução normativa DNRC
3 NUM_ORD 1 🔒 Fixo Número de ordem do instrumento de escrituração. Sempre 1
4 NAT_LIVRO IND_ESC_DESCR[ind_esc] 🧮 Derivado Natureza do livro — mesmo texto usado no I030 NAT_LIVR. Fallback: 'Livro Diário (Completo sem escrituração auxiliar).'
5 NOME company.name 🏢 Empresa Razão social
6 QTD_LIN 0 (inicial) 🧮 Recalculado Quantidade total de linhas do arquivo. Recalculado por ArquivoDigital.prepare() para o total real — mesmo valor que I030.QTD_LIN prepare()
7 DT_INI_ESCR self.date_ini ⚙️ Wizard Data de início da escrituração
8 DT_FIN_ESCR self.date_fim ⚙️ Wizard Data de fim da escrituração

Mecanismo de recálculo do QTD_LIN

# Em ArquivoDigital.prepare() — arquivos.py, linhas 70-72:
encerramentoJ = [x for x in self._blocos['J'].registros
                 if isinstance(x, RegistroJ900)]
encerramentoJ[0].QTD_LIN = reg_count

O mesmo reg_count que é usado no I030 é aplicado aqui — garante consistência.

Referência técnica

enviar_registro_J900() — linhas 873–888


Registro J930 — Identificação dos Signatários da Escrituração

Finalidade

Identifica o contador responsável pela ECD. É obrigatório — a ECD não pode ser transmitida sem signatário. O PVA exige ao menos o signatário tipo '900' (Contador).

Validação bloqueante — detalhada

contador_id = self.company_id.l10n_br_contador_partner_id

if not contador_id:
    action = {
        "view_mode": "form",
        "res_model": "res.company",
        "type": "ir.actions.act_window",
        "res_id": self.company_id.id,
        "views": [[self.env.ref("base.view_company_form").id, "form"]],
    }
    raise RedirectWarning(
        "Informar o contador da empresa",
        action,
        "IR PARA EMPRESA",
    )

Campos — detalhamento completo

# Campo SPED Valor Origem Significado legal Configurável?
1 IDENT_NOM contador.name or '' 👤 Contato Nome completo do signatário ✅ Contatos → Nome
2 IDENT_CPF_CNPJ _format_cnpj_cpf(contador.l10n_br_cpf) 👤 Contato CPF do contador (sem formatação). Se vazio, grava string vazia ✅ Contatos → CPF
3 IDENT_QUALIF 'Contador' 🔒 Fixo Qualificação do assinante. Sempre "Contador" — valores possíveis seriam "Representante Legal", "Empresário", etc.
4 COD_ASSIN '900' 🔒 Fixo Código do assinante conforme tabela RFB. 900=Contador/Contabilista. Outros: 001=Signatário da ECD, 203=Diretor, etc.
5 IND_CRC contador.l10n_br_crc or '' 👤 Contato Número do registro no CRC (Conselho Regional de Contabilidade) ✅ Contatos → CRC
6 EMAIL contador.email or '' 👤 Contato E-mail para comunicações da RFB ✅ Contatos → E-mail
7 FONE _format_fone(contador.phone) 👤 Contato Telefone (somente dígitos, sem DDI +55) ✅ Contatos → Telefone
8 UF_CRC contador.state_id.code or '' 👤 Contato UF do CRC do contador ✅ Contatos → Estado
9 NUM_SEQ_CRC '' 🔒 Fixo Sequencial do CRC. Não utilizado
10 DT_CRC '' 🔒 Fixo Data de validade do CRC. Não utilizado
11 IND_RESP_LEGAL 'N' 🔒 Fixo Indicador de responsável legal. S=sim, N=não. O contador não é responsável legal — a empresa deveria ter um segundo J930 com o representante legal, mas isso não está implementado

Formatação do telefone — detalhada

def _format_fone(fone):
    fone = fone or ""
    fone = "".join([s for s in fone if s.isdigit()])  # Remove tudo que não é dígito
    if len(fone) > 11 and fone[0:2] == "55":
        fone = fone[2:]  # Remove o código do país (+55)
    return fone
Entrada Saída Regra aplicada
+55 (11) 99999-9999 11999999999 Remove +, (), -, espaços e o 55
(11) 3333-4444 1133334444 Remove (, ), -, espaços
11999998888 11999998888 Sem transformação
5511999998888 11999998888 Remove 55 porque tamanho > 11
5599998888 5599998888 Mantém — tamanho ≤ 11 (não remove 55)
None '' Fallback

O sistema gera apenas um J930 (o contador). A RFB aceita, mas idealmente deveria haver um segundo J930 com: - COD_ASSIN = '001' (Signatário da ECD com procuração) - IND_RESP_LEGAL = 'S' - Dados do responsável legal da empresa

Referência técnica

enviar_registro_J930() — linhas 890–938


Registro J990 — Encerramento do Bloco J

Cálculo

QTD_LIN_J = len(_arq._blocos['J'].registros)

Referência técnica

enviar_registro_J990() — linha 940


Registros Não Implementados (Bloco J)

Registro Classe Finalidade Status no código Campos implementados
J210 RegistroJ210 DLPA/DMPL — Demonstração de Lucros/Prejuízos Acumulados ou Demonstração de Mutações do PL Comentado — método existe (linha 826) com valores vazios/zeros IND_TIP, COD_AGL, DESCR_COD_AGL, VL_CTA_INI, IND_DC_CTA_INI, VL_CTA_FIN, IND_DC_CTA_FIN
J215 RegistroJ215 Fato Contábil — fatos contábeis que alteram o PL (detallhe do J210) Comentado — método existe (linha 844) com valores vazios COD_HIST_FAT, DESC_FAT, VL_FAT_CONT, IND_DC_FAT
J800 RegistroJ800 Outras Informações — documentos em formato RTF (notas explicativas, relatório de administração) Comentado — método existe (linha 858) com valores vazios TIPO_DOC, DESC_RTF, HASH_RTF, ARQ_RTF, IND_FIM_RTF

Como habilitar se necessário: Os métodos já existem. Basta descomentar as linhas 194–196 do gerar_sped() e implementar a lógica real de preenchimento (os valores atuais são todos vazios/zero e o PVA rejeitaria).


Referências