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_ofpara consolidar todos os subgrupos e contas abaixo de cada grupo. O I155 calcula conta a conta individualmente.
Condição de supressão¶
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_countverifica 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)¶
| 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 porcod_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_ofaqui é 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:
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 |
Limitação — sem representante legal¶
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¶
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).